Your achievements

Level 1

0% to

Level 2

Tip /
Sign in

Sign in to Community

to gain points, level up, and earn exciting badges like the new
Bedrock Mission!

Learn more

View all

Sign in to view all badges

SOLVED

AEM | SPA Create custom React components with responsive grid to allow adding other react components

Avatar

Level 1

Hello,

Does anyone know how to create a react component with responsive grid and options so that we can drag and add other additional components?

The goal for us is to create a popup component and within this popup component we are going to name the component within the options menu. In the meantime also has the ability to drag and add other react components we have created. The purpose of the Popup Name is to be able to pass the data into react and link it with other AEM React component so that it can be triggered with an event.


For example
1. Add a button component

2. within the button component, there a option we can indicate which popup should it open if a user clicks on it

BingChilling_2-1669825536023.png

3. once the user clicks on the button, it then looks for the popup component with the same name and the popup should be revealed.

BingChilling_1-1669825376497.png

4. within the popup we can add whatever the component we want, for example, video component or image component.

BingChilling_0-1669825351857.png

Problem:

1. I tried several ways of doing it, adding responsive grid from @adobe/aem-react-editable-components into the functional popup component we created, it didnt work.

2. we also tried creating a class based popup component which extents from the responsive grid, this works however when we try to add the options menu in by creating the java file and the .content.xml, we weren't able to add any of the components in(even though the options are there, but its not responding). However, the name of the popup component is being passed into React correctly.

BingChilling_3-1669825892826.png

when we console.log props, we do see the name of the component is being passed into react correctly along with other data

BingChilling_7-1669826260706.png

below are the xml files created

_cq_dialog is there for the option menu to work.

_cq_template, new, and _cq_editConfig is there for the responsive grid of adding additional components to work.

BingChilling_8-1669826373047.png

it works all fine, until we add the java files which handles the functionality of passing the data from the options menu into react

Note: I also see other people trying to achieve the same thing as us, but the solutions they get points them to the SPA documentation which did not mention any complex use case.

1 Accepted Solution

Avatar

Correct answer by
Community Advisor

Hi,

you need to add java model as well for a container component

e.g.

 

package com.community.aemlab.spa.core.models.impl;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javax.inject.Inject;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Source;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.factory.ModelFactory;

import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.export.json.SlingModelFilter;
import com.adobe.cq.wcm.core.components.models.Container;
import com.adobe.cq.wcm.core.components.models.ListItem;
import com.community.aemlab.spa.core.models.CustomGrid;
import com.day.cq.wcm.api.TemplatedResource;
import com.day.cq.wcm.api.components.ComponentManager;
import com.drew.lang.annotations.NotNull;

@Model(adaptables = SlingHttpServletRequest.class, adapters = { CustomGrid.class,
		ComponentExporter.class }, resourceType = CustomGridImpl.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class CustomGridImpl implements CustomGrid, Container {

	static final String RESOURCE_TYPE = "aemlab-spa/components/custom/customgrid";

	@Self
	private SlingHttpServletRequest request;
	
	@Inject @Source("script-bindings")
	Resource resource;

	@ValueMapValue
	private String gridLayout;

	@ValueMapValue
	private String title;

	@ValueMapValue
	private String description;

	@Override
	public String getDescription() {
		return description;
	}

	@Override
	public String getTitle() {
		return title;
	}

	@Override
	public String getGridLayout() {
		return gridLayout;
	}

	@Override
	public String getExportedType() {
		return CustomGridImpl.RESOURCE_TYPE;
	}
	
	/**
     * The model factory.
     */
    @OSGiService
    protected ModelFactory modelFactory;
    
    /**
     * The sling model factory service.
     */
    @OSGiService
    protected SlingModelFilter slingModelFilter;
	
	/**
     * The list of child items.
     */
    protected List<ListItem> items;

    /**
     * The list of child resources that are components.
     */
    protected List<Resource> childComponents;

    /**
     * The child resources to be exported.
     */
    protected List<Resource> filteredChildComponents;

    /**
     * Map of the child items to be exported wherein the key is the child name, and the value is the child model.
     */
    protected Map<String, ? extends ComponentExporter> itemModels;

    /**
     * The name of the child resources in the order they are to be exported.
     */
    private String[] exportedItemsOrder;


	@NotNull
	@Override
	public Map<String, ? extends ComponentExporter> getExportedItems() {
		 if (itemModels == null) {
	            itemModels = getItemModels(request, ComponentExporter.class);
	        }
	        return itemModels;
	}

	@NotNull
	@Override
	public String[] getExportedItemsOrder() {
		if (exportedItemsOrder == null) {
            Map<String, ? extends ComponentExporter> models = getExportedItems();
            if (!models.isEmpty()) {
                exportedItemsOrder = models.keySet().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
            } else {
                exportedItemsOrder = ArrayUtils.EMPTY_STRING_ARRAY;
            }
        }
        return Arrays.copyOf(exportedItemsOrder, exportedItemsOrder.length);
	}
	
	/**
     * Get the models for the child resources as provided by {@link AbstractContainerImpl#getFilteredChildren()}.
     *
     * @param request The current request.
     * @param modelClass The child model class.
     * @return Map of models wherein the key is the child name, and the value is it's model.
     */
    protected Map<String, ComponentExporter> getItemModels(@NotNull final SlingHttpServletRequest request,
                                                           @NotNull final Class<ComponentExporter> modelClass) {
        Map<String, ComponentExporter> models = new LinkedHashMap<>();
        getFilteredChildren().forEach(child -> {
            ComponentExporter model = modelFactory.getModelFromWrappedRequest(request, child, modelClass);
            if (model != null) {
                models.put(child.getName(), model);
            }
        });
        return models;
    }
    
    /**
     * Return (and cache) the list of children resources that are components, filtered by the Sling Model Filter. This
     * should only be used for JSON export, for other usages refer to {@link AbstractContainerImpl#getChildren}.
     *
     * @return The list of children resources available for JSON export.
     */
    @NotNull
    protected List<Resource> getFilteredChildren() {
        if (filteredChildComponents == null) {
            filteredChildComponents = new LinkedList<>();
            slingModelFilter.filterChildResources(getChildren())
                .forEach(filteredChildComponents::add);
        }
        return filteredChildComponents;
    }
    
    /**
     * Return (and cache) the list of children resources that are components
     *
     * @return List of all children resources that are components.
     */
    @NotNull
    protected List<Resource> getChildren() {
        if (childComponents == null) {
            Resource effectiveResource = this.getEffectiveResource();
            childComponents = Optional.ofNullable(request.getResourceResolver().adaptTo(ComponentManager.class))
                .map(componentManager ->
                    StreamSupport.stream(effectiveResource.getChildren().spliterator(), false)
                        .filter(res -> Objects.nonNull(componentManager.getComponentOfResource(res))))
                .orElseGet(Stream::empty)
                .collect(Collectors.toList());
        }
        return childComponents;
    }
    
    /**
     * Get the effective {@link TemplatedResource} for the current resource.
     *
     * @return The TemplatedResource, or the current resource if it cannot be adapted to a TemplatedResource.
     */
    @NotNull
    protected Resource getEffectiveResource() {
        if (this.resource instanceof TemplatedResource) {
            return this.resource;
        }
        return Optional.ofNullable((Resource)this.resource.adaptTo(TemplatedResource.class))
                .orElse(Optional.ofNullable((Resource)this.request.adaptTo(TemplatedResource.class))
                .orElse(this.resource));
    }

}

View solution in original post

1 Reply

Avatar

Correct answer by
Community Advisor

Hi,

you need to add java model as well for a container component

e.g.

 

package com.community.aemlab.spa.core.models.impl;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javax.inject.Inject;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Source;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.factory.ModelFactory;

import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.export.json.SlingModelFilter;
import com.adobe.cq.wcm.core.components.models.Container;
import com.adobe.cq.wcm.core.components.models.ListItem;
import com.community.aemlab.spa.core.models.CustomGrid;
import com.day.cq.wcm.api.TemplatedResource;
import com.day.cq.wcm.api.components.ComponentManager;
import com.drew.lang.annotations.NotNull;

@Model(adaptables = SlingHttpServletRequest.class, adapters = { CustomGrid.class,
		ComponentExporter.class }, resourceType = CustomGridImpl.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class CustomGridImpl implements CustomGrid, Container {

	static final String RESOURCE_TYPE = "aemlab-spa/components/custom/customgrid";

	@Self
	private SlingHttpServletRequest request;
	
	@Inject @Source("script-bindings")
	Resource resource;

	@ValueMapValue
	private String gridLayout;

	@ValueMapValue
	private String title;

	@ValueMapValue
	private String description;

	@Override
	public String getDescription() {
		return description;
	}

	@Override
	public String getTitle() {
		return title;
	}

	@Override
	public String getGridLayout() {
		return gridLayout;
	}

	@Override
	public String getExportedType() {
		return CustomGridImpl.RESOURCE_TYPE;
	}
	
	/**
     * The model factory.
     */
    @OSGiService
    protected ModelFactory modelFactory;
    
    /**
     * The sling model factory service.
     */
    @OSGiService
    protected SlingModelFilter slingModelFilter;
	
	/**
     * The list of child items.
     */
    protected List<ListItem> items;

    /**
     * The list of child resources that are components.
     */
    protected List<Resource> childComponents;

    /**
     * The child resources to be exported.
     */
    protected List<Resource> filteredChildComponents;

    /**
     * Map of the child items to be exported wherein the key is the child name, and the value is the child model.
     */
    protected Map<String, ? extends ComponentExporter> itemModels;

    /**
     * The name of the child resources in the order they are to be exported.
     */
    private String[] exportedItemsOrder;


	@NotNull
	@Override
	public Map<String, ? extends ComponentExporter> getExportedItems() {
		 if (itemModels == null) {
	            itemModels = getItemModels(request, ComponentExporter.class);
	        }
	        return itemModels;
	}

	@NotNull
	@Override
	public String[] getExportedItemsOrder() {
		if (exportedItemsOrder == null) {
            Map<String, ? extends ComponentExporter> models = getExportedItems();
            if (!models.isEmpty()) {
                exportedItemsOrder = models.keySet().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
            } else {
                exportedItemsOrder = ArrayUtils.EMPTY_STRING_ARRAY;
            }
        }
        return Arrays.copyOf(exportedItemsOrder, exportedItemsOrder.length);
	}
	
	/**
     * Get the models for the child resources as provided by {@link AbstractContainerImpl#getFilteredChildren()}.
     *
     * @param request The current request.
     * @param modelClass The child model class.
     * @return Map of models wherein the key is the child name, and the value is it's model.
     */
    protected Map<String, ComponentExporter> getItemModels(@NotNull final SlingHttpServletRequest request,
                                                           @NotNull final Class<ComponentExporter> modelClass) {
        Map<String, ComponentExporter> models = new LinkedHashMap<>();
        getFilteredChildren().forEach(child -> {
            ComponentExporter model = modelFactory.getModelFromWrappedRequest(request, child, modelClass);
            if (model != null) {
                models.put(child.getName(), model);
            }
        });
        return models;
    }
    
    /**
     * Return (and cache) the list of children resources that are components, filtered by the Sling Model Filter. This
     * should only be used for JSON export, for other usages refer to {@link AbstractContainerImpl#getChildren}.
     *
     * @return The list of children resources available for JSON export.
     */
    @NotNull
    protected List<Resource> getFilteredChildren() {
        if (filteredChildComponents == null) {
            filteredChildComponents = new LinkedList<>();
            slingModelFilter.filterChildResources(getChildren())
                .forEach(filteredChildComponents::add);
        }
        return filteredChildComponents;
    }
    
    /**
     * Return (and cache) the list of children resources that are components
     *
     * @return List of all children resources that are components.
     */
    @NotNull
    protected List<Resource> getChildren() {
        if (childComponents == null) {
            Resource effectiveResource = this.getEffectiveResource();
            childComponents = Optional.ofNullable(request.getResourceResolver().adaptTo(ComponentManager.class))
                .map(componentManager ->
                    StreamSupport.stream(effectiveResource.getChildren().spliterator(), false)
                        .filter(res -> Objects.nonNull(componentManager.getComponentOfResource(res))))
                .orElseGet(Stream::empty)
                .collect(Collectors.toList());
        }
        return childComponents;
    }
    
    /**
     * Get the effective {@link TemplatedResource} for the current resource.
     *
     * @return The TemplatedResource, or the current resource if it cannot be adapted to a TemplatedResource.
     */
    @NotNull
    protected Resource getEffectiveResource() {
        if (this.resource instanceof TemplatedResource) {
            return this.resource;
        }
        return Optional.ofNullable((Resource)this.resource.adaptTo(TemplatedResource.class))
                .orElse(Optional.ofNullable((Resource)this.request.adaptTo(TemplatedResource.class))
                .orElse(this.resource));
    }

}