AEM 6.4 with SPA Editor - how to use the experience fragments
Hi,
Using AEM 6.4 with SPA Editor, is there any example/sample of how to use the experience fragments inside the SPA site?
Hi,
Using AEM 6.4 with SPA Editor, is there any example/sample of how to use the experience fragments inside the SPA site?
Following the approach suggested by JaideepBrar for 6.5 I've managed to get the contents of experience fragments displaying on a react app. This is based on the AEM react SPA tutorial.
First, a model interface for the component that extends the ContainerExporter interface was created. (FYI, the ContainerExporter interface is referenced in some comments within the BaseComponentExporter class created in the tutorial.)
package com.adobe.aem.guides.wkndevents.core.models;
import com.adobe.cq.export.json.ComponentExporter;
import javax.annotation.Nonnull;
import com.adobe.cq.export.json.ContainerExporter;
import org.osgi.annotation.versioning.ConsumerType;
@ConsumerType
public interface ExperienceFragment extends ContainerExporter {@Nonnull
default String getExportedType() {throw new UnsupportedOperationException();
}
}
Some of the logic from the HierarchyPageImpl class of the tutorial was borrowed to implement the interface methods. But the "getItemModels" method was modified so that it retrieved the children of the Experience Fragment page content:
package com.adobe.aem.guides.wkndevents.core.models.impl;
import com.adobe.aem.guides.wkndevents.core.models.ExperienceFragment;
import com.adobe.cq.export.json.ComponentExporter;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import com.adobe.cq.export.json.SlingModelFilter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.jcr.resource.api.JcrResourceConstants;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.factory.ModelFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashMap;
import java.util.Map;
@Model(
adaptables = {SlingHttpServletRequest.class},
adapters = {ExperienceFragment.class, ComponentExporter.class},
resourceType = {ExperienceFragmentImpl.RESOURCE_TYPE}
)
@Exporter(
name = "jackson",
extensions = {"json"}
)
public class ExperienceFragmentImpl implements ExperienceFragment {
protected static final String RESOURCE_TYPE = "cq/experience-fragments/editor/components/experiencefragment";
private static final Logger LOG = LoggerFactory.getLogger(ExperienceFragmentImpl.class);
private static final String JCR_CONTENT = "/jcr:content";
@Self
private SlingHttpServletRequest request;@Inject
private ResourceResolver resourceResolver;@Inject
private Resource resource;@Inject
private ModelFactory modelFactory;@Inject
private SlingModelFilter slingModelFilter;@ValueMapValue(
injectionStrategy = InjectionStrategy.OPTIONAL
)private String fragmentPath;
private Map<String, ComponentExporter> childModels = null;
public ExperienceFragmentImpl() {
LOG.debug("Creating ExperienceFragmentImpl");
}
@Override
@Nonnull
public String getExportedType() {return this.resource.getResourceType();
}
@Nonnull
@Override
public Map<String, ? extends ComponentExporter> getExportedItems() {if (childModels == null) {
childModels = getItemModels(request, ComponentExporter.class);
}
LOG.debug("childModels: {}", childModels);
return childModels;
}
/**
* Returns a map (resource name => Sling Model class) of the given resource children's Sling Models that can be adapted to {@link T}.
*
* @param slingRequest The current request.
* @param modelClass The Sling Model class to be adapted to.
* @return Returns a map (resource name => Sling Model class) of the given resource children's Sling Models that can be adapted to {@link T}.
*/
@Nonnull
private <T> Map<String, T> getItemModels(@Nonnull SlingHttpServletRequest slingRequest,@Nonnull Class<T> modelClass) {
Map<String, T> itemWrappers = new LinkedHashMap<>();
if(StringUtils.isBlank(fragmentPath)) {
return itemWrappers;
}
Resource experienceFragmentContent = resourceResolver.getResource(fragmentPath + JCR_CONTENT);
if (experienceFragmentContent == null) {
return itemWrappers;
}
Iterable<Resource> iterable = slingModelFilter.filterChildResources(experienceFragmentContent.getChildren());
if (iterable == null) {
return itemWrappers;
}
for (final Resource child : iterable) {
LOG.debug("child.getPath(): {}", child.getPath());
T model = modelFactory.getModelFromWrappedRequest(slingRequest, child, modelClass);
LOG.debug("model: {}", model);
itemWrappers.put(child.getName(), model);
}
return itemWrappers;
}
@Nonnull
@Override
public String[] getExportedItemsOrder() {Map<String, ? extends ComponentExporter> models = getExportedItems();
if (models.isEmpty()) {
return ArrayUtils.EMPTY_STRING_ARRAY;
}
return models.keySet().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
}
}
Because the ContainerExporter interface was used in the component exporter the Container module to be used from the cq-react-editable-components JavaScript library as opposed to the Component module:
/*
ExperienceFragment.js
Maps to cq/experience-fragments/editor/components/experiencefragment
*/
import React from 'react';import {MapTo, Container} from '@adobe/cq-react-editable-components';
require('./ExperienceFragment.scss');
/**
* Default Edit configuration for the Text component that interact with the Core ExperienceFragment component.
*
* @type EditConfig
*/
const ExperienceFragmentConfig = {emptyLabel: 'Experience Fragment',
isEmpty: function(props) {
var itemsOrder = props["cqItemsOrder"];
return !(itemsOrder && itemsOrder.length);
}
};
/**
* Text React component
*/
export default class ExperienceFragment extends Container {render() {
return (<div className="ExperienceFragment">
{ this.childComponents }
</div>);
}}
MapTo('cq/experience-fragments/editor/components/experiencefragment')(ExperienceFragment, ExperienceFragmentConfig);
But this will only show components in the experience fragment where the type has already been mapped. So after completing the tutorial it will only display the image, text and list components used in a fragment.
I hope this answers the question.
Enter your E-mail address. We'll send you an e-mail with instructions to reset your password.