Deep diving into Sling models PT5

10 juli 2020by Dylan Reniers

Part 5: Resources, resources everywhere!

In this blog post I will explain how I’ve managed to reuse existing components within other components, mitigating the need to copy/paste or even having to create a template for it.
The idea behind this blog post was inspired by a former team member who I used to work with at VRT.
If you’re reading this, Bert, I hope you remember that Section component for sporza.be, with the different layout options.
That component has made me re-think about how I can leverage the usage of Resources to reuse my components.

The traditional way of working

During the time you’ve been developing components for AEM, you have probably built some kind of component that would benefit from using an already existing component.
Let’s say we have an image component and we want to create an image with text component. How do you go about this?
If the scenario I just explained sounds familiar to you, go and have a look at how you’ve actually built that component before you read on. Then come back here and I’ll try to guess how you probably built that component by using my example of the image and image with text component.

All done? Let’s figure out if I was right or not!

You probably created a template out of the HTL you made for the image component, because that one has all the correct classes and everything and used that for both your image and image with text component. At first glance, that seems a pretty good and straightforward thing to do, since now we have only one place to manage the HTL for both of those components.

But what about the backend logic that fetches all the renditions for your image, to create the proper media set to make sure you’re loading the corresponding image for each type of device?
Sure, you can create a separate service for this, inject it in the model you use for the image component and the model for the image with text component and pass your renditions as parameters to the HTL template.

But wouldn’t this solution be complicating things? Let’s have a closer look.

Using Sling to render your own custom resources

In order for us to reuse a component completely, we will be creating our own resources and use the Sling resource rendering mechanism to render a component using the resource that we created ourselves. A crucial part in this approach is the usage of a SyntheticResource. A SyntheticResource is a resource that does not actually exist in the JCR, but that we can treat as if it does.
The problem with a SyntheticResource is that there is no real easy way to manipulate its data. If you look at what you can do with it, your options are pretty limited and there is no real way for you to give it your own data.
In order for us to have control over what data (read: value map) this SyntheticResource holds, we can use the following helper class, which is heavily inspired by ACS AEM Examples:

package eu.digitalum.resource;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceWrapper;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.CompositeValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;

import java.util.Map;

public class CompositeResourceWrapper extends ResourceWrapper {
    private final ValueMap properties;

    public CompositeResourceWrapper(final Resource resource, final Map<String, Object> properties) {
        super(resource);
        this.properties = new CompositeValueMap(new ValueMapDecorator(properties), resource.getValueMap());
    }

    public CompositeResourceWrapper(final Resource resource, final ValueMap properties) {
        super(resource);
        this.properties = new CompositeValueMap(properties, resource.getValueMap());
    }

    @Override
    public final <AdapterType> AdapterType adaptTo(final Class<AdapterType> type) {
        if (type == ValueMap.class) {
            return (AdapterType) this.properties;
        }
        return super.adaptTo(type);
    }

    @Override
    public ValueMap getValueMap() {
        return properties;
    }
}

So, what is this class used for? We basically add another wrapper around any Resource object, in our case the SyntheticResource, but instead of using the properties of the actual resource, we use the Map that we provided in the constructor.
This is required because a SyntheticResource does not contain any values by itself. If we pass our SyntheticResource through the Sling rendering mechanism, you would see that quite often a Resource is either adapted to a ValueMap or that the getValueMap method is called. In both scenarios we return our own properties in order for Sling to use during rendering.

Before I will show you the example code, let’s have a quick look at the following diagrams to see and understand the relationships between the components and Java classes:

HTL:

Java:

The following code snippet is the implementation of the getImage method of the TextWithImage Sling model:

@Self
private SlingHttpServletRequest self;

@Override
public Resource getImage() {
    Map<String, Object> properties = ImmutableMap.<String, Object>builder()
            .put("fileReference", "/content/dam/mycompany/image.jpeg")
            .build();
    self.setAttribute("requestAttributeKey", "requestAttributeValue");
    final Resource resource = new SyntheticResource(self.getResourceResolver(), self.getResource().getPath() + "/image", "mycompany/components/image");
    return new CompositeResourceWrapper(resource, properties);
}

So, if in our HTL of the mycompany/components/image-text component we use:

<sly data-sly-use.model="${eu.digitalum.textimage.TextWithImage}" data-sly-resource="${model.image}"/>

Then we have successfully managed to completely reuse mycompany/components/image component and passed it any properties of our choice. This allows us to fetch data from other places in the JCR or even entirely different systems and inject them as properties that are required by the Sling model for the mycompany/components/image component, since all we need to know is the property names of the model.

In the example we specified our own value for the ‘fileReference’ resource property and the request attribute ‘requestAttributeKey’, which both can be injected and used within the Sling model of the mycompany/components/image component. This allows us to render this component as a standalone component.

I hope this article has been informative and has given you some new insights on how to reuse components by just manipulating your data. If you still have some questions, feel free to get in touch and we will gladly help you out.

Until next time!

CONTACTHoofdkantoor
Klaverbladstraat 7a b5,
3560 Lummen - BE
JONG EN HIP?Gebruik onze social links
U kan ons ook contacteren via
onze verschillende social media kanalen.
JOBSOok Joker worden?
Bezoek onze carrière pagina voor
openstaande opportuniteiten.
ContactHoofdkantoor
Klaverbladstraat 7a b5, 3560 Lummen - BE
JONG & HIP?Gebruik onze Social links
U kan ons ook contacteren via onze verschillende social media kanalen.