Sling Models in AEM: Decorating Component Dialog Values on the Backend
Aug 14, 2023
Developing maintainable editable/
This can occur due to the complex backend interactions that need to happen, from simply cleaning user input to calling external APIs to get data based on authored content. Editors/ Authors need to be able to show the final output so they can have instant feedback of the consequences of the data changes they make.
Sling Models help us ensure our components encapsulate any decoration work delegating it to the backend during server-side rendering, exposing the final result using HTL that can be visualized in edit mode while maximizing their re-usability and editor acceptance of the solution.
Sling Models are annotation-driven Plain Old Java Objects (POJOs) that facilitate the mapping of data from the JCR attributes of a resource to Java™ variables. They can be used to accomplish different requirements from accessing/decorating resource's JCR values to accessing OSGi services directly in HTL at server-side rendering time.
In this blog post, we will demonstrate the first scenario. This exercise aims to show how to access the HTL component's JCR attributes using a Sling Model that will retrieve decorated values.
Step 1: Define/create a parent package for all SlingModels within the application.
Place all your Sling Models under a common parent location (if possible) so it's easier to access/
Step 2: Expose the Sling Models' parent package to AEM.
There are two steps to accomplish this:
Step 2.1: Add bnd-maven-plugin(recommended)
Add the Sling Models bnd PlugIn (bnd-maven-plugin) to your project's pom.xml to construct an OSGi Manifest file for the bundle and other required metadata based on annotations and dependencies discovered in the code. If you don't use this plugin, you are required to edit the Bundle Manifest Configuration Manually, which is not recommended to avoid/reduce human errors.
Note: bnd-maven-plugin
has been preferred instead of the previous maven-bundle-plugin, and it comes by default with the latest aem-project-archetype (recommended).
bod-maven-plugin
<!-- MAIN pom.xml -->
</properties>
<bnd.version>5.1.2</bnd.version>
<componentGroupName>Oshyn Demo</componentGroupName>
</properties>
<!-- BND Maven Plugin -->
<plugin>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd-maven-plugin</artifactId>
<version>${bnd.version}</version>
<executions>
<execution>
<id>bnd-process</id>
<goals>
<goal>bnd-process</goal>
</goals>
<configuration>
<bnd><![CDATA[Bundle-Category: ${componentGroupName}
# export all versioned packages except for conditional ones (https://github.com/bndtools/bnd/issues/3721#issuecomment-579026778)
-exportcontents: ${removeall;${packages;VERSIONED};${packages;CONDITIONAL}}
# reproducible builds (https://github.com/bndtools/bnd/issues/3521)
-noextraheaders: true
-snapshot: SNAPSHOT
Bundle-DocURL:
-plugin org.apache.sling.caconfig.bndplugin.ConfigurationClassScannerPlugin
-plugin org.apache.sling.bnd.models.ModelsScannerPlugin]]></bnd>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.caconfig.bnd-plugin</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.bnd.models</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>scriptingbundle-maven-plugin</artifactId>
<version>0.5.0</version>
</dependency>
</dependencies>
</plugin>
<!-- CORE pom.xml -->
<!-- BND Maven Plugin -->
<plugin>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd-maven-plugin</artifactId>
<executions>
<execution>
<id>bnd-process</id>
<goals>
<goal>bnd-process</goal>
</goals>
<configuration>
<bnd><![CDATA[
Import-Package: javax.annotation;version=0.0.0,*
]]></bnd>
</configuration>
</execution>
</executions>
</plugin>
Step 2.2: Add a package-info.java file
Create a package-info.java
file in the parent package that contains your sling models, for example:
package-info.java
@Version("1.0")
package com.your.sling.models.parent.package;
import org.osgi.annotation.versioning.Version;
For this example, our package-info.java
files look like this:
package-info.java
@Version("1.0")
package com.oshyndemo.core.models;
import org.osgi.annotation.versioning.Version;
Step 3: Identify the JCR elements you want to access using a SlingModel
In this example, you will access the authored elements of a text component(values exposed in the component dialog).
When the latest aem-project-archetype is used to create a project, some components are automatically created in the project's /app directory to overlay the OOTB functionality provided by Adobe and enforce Adobe best practices, particularly the Text component.
When you add the Text component to an AEM page of your project, if you double click the component in the page in edit mode, then you can edit the component text content and the id of the wrapper element in an AEM component dialog:
All of these functionalities come out of the box when using the aem-project-archetype. A scripting file needs to be added to overlay the component's OOTB behavior using HTL code.
These are the steps:
Step 3.1: Find the proxied Text component of the current project
Step 3.2: Find the names of the component's dialog elements
Check at /apps+sling:resourceType (in this case /apps/core/wcm/components/text/v2/text
) for the dialog items nodes. Those names will be referenced from the Sling Model using a @ValueMapValue
annotation.
Step 3.3: Access the Sling Model in HTL
A new text.html file needs to be created inside the project's text Component node:
Notice the path will depend on the name you gave to your project in the creation moment. In this example, our project is called oshynDemo, but the rest of the path should be identical. At this point, there's an HTL file where HTL code can be executed. We can also access a Sling Model, so let's create that Sling Model to access the Text component attributes.
Step 4: Create your Sling model
There are two steps to accomplish this:
Step 4.1: Define the model interface
As in any other interface, here we define the methods/functionalities wanted to be exposed. In this case, we want to access the text and id fields of the Text component.
Basic Sling Model Interface
package com.oshyndemo.core.models.sling;
public interface SlingModelTextDecorator {
String getText();
String getId();
}
Step 4.2: Create a corresponding implementation
The Sling Model will adapt itself to the component resource and will have access to the component’s JCR content, mainly the text and id fields, which can be accessed/mapped by annotating attribute variables with @ValueMapValue
. They will also be exposed using accessor methods.
Basic Sling Model Implementation
package com.oshyndemo.core.models.sling.impl;
import org.apache.commons.lang3.StringUtils;
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.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import com.oshyndemo.core.models.sling.SlingModelTextDecorator;
@Model(
adaptables = {Resource.class, SlingHttpServletRequest.class},
adapters = {SlingModelTextDecorator.class},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class SlingModelTextDecoratorImpl implements SlingModelTextDecorator {
private static final String DASH = "-";
private static final String WHITE_SPACE = " ";
@ValueMapValue
private String text;
@ValueMapValue
private String id;
@Override
public String getText(){
return decorateText(text) ;
}
@Override
public String getId(){
return decorateText(id);
}
//we are replacing the whitespaces with dashes as an example, but you can do whatever you want here. This is just an annotated POJO
private String decorateText(String inputText){
return StringUtils.isEmpty(inputText)? inputText : inputText.replace(WHITE_SPACE, DASH);
}
}
This is a POJO, so you can use standard Java code to decorate your dialog/authored values as required, you can access an OSGi service to process your text or even call an external API, but that's outside the scope of this post. If the DefaultInjectionStrategy.OPTIONAL to allow empty values is not specified, then a null checking needs to be performed. For more details on other injection strategies and annotations available, please check the Sling documentation.
Step 5: Use your Sling Model inside HTL
There are two steps to accomplish this:
Step 5.1: Expose your sling model to HTL
Go to your text.html file and specify the fully-qualified name of the Sling Model interface you want to use and get an instance of its implementation (dependency injection) and define the name of the local reference used to access that instance(slingModelText in this case):
Exposing Sling Model to HTL
<sly data-sly-use.slingModelText="com.oshyndemo.core.models.sling.SlingModelTextDecorator" />
Step 5.2: Access your sling model methods in HTL
Use the reference defined in the previous step.
Using Sling Model in HTL
<h3>Component Text:[ ${slingModelText.text @context = 'html'} ]</h3>
<h4>Component Id:[ ${slingModelText.id @context = 'html'} ]</h4>
Notice that:
-
We are not decorating the component dialog values in the HTL; we are just delegating that work to the backend (Sling Model).
-
We are not accessing the attributes but their getter methods; we are calling getter methods without the get prefix by convention.
-
We are using @context = 'html' to escape the text as HTML, removing markup that may contain XSS risks.
Step 6: Add text content to the component and verify it's been decorated
Go to your page, double-click to open the Text component dialog, enter values for the text area and the id, and check the decorated text value on the page.
As you can see, the sling model decoration can be challenging to implement. However, it does help separate responsibilities of data decoration to the backend while leaving the display of final information to the frontend layer making your AEM components more portable, reliable, and maintainable.
Final Troubleshooting Notes
If you get a message saying [class.name.used.in.htl]
"cannot be resolved to a type", validate your SlingModel is registered at http://localhost:4502/system/console/status-adapters
, you should see something like:
Registered Sling Models
Adaptable: org.apache.sling.api.SlingHttpServletRequest
Providing Bundle: org.apache.sling.models.impl
Available Adapters:
* com.oshyndemo.core.models.sling.SlingModelTextDecorator
* com.oshyndemo.core.models.sling.impl.SlingModelTextDecoratorImpl
-
If it does not appear there, then check
bnd-maven-plugin
has been correctly configured. -
In case it does appear there, then:
-
Validate you are using the correct fully qualified name of your SlingModel Interface in HTL, as specified in step 5.1.
-
Validate you have a
package-info.java
file in the Sling Models' parent directory. If not, go ahead and create it as specified in step 2.2.
-
Related Insights
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.