Create a Custom Component custom-component
Learn how to create a custom component to be used with the AEM SPA Editor. Learn how to develop author dialogs and Sling Models to extend the JSON model to populate a custom component.
Objective
- Understand the role of Sling Models in manipulating the JSON model API provided by AEM.
- Understand how to create AEM component dialogs.
- Learn to create a custom AEM Component that is compatible with the SPA editor framework.
What you will build
The focus of previous chapters was developing SPA components and mapping them to existing AEM Core Components. This chapter focuses on how to create and extend new AEM components and manipulate the JSON model served by AEM.
A simple Custom Component
illustrates the steps needed to create a net-new AEM component.
Prerequisites
Review the required tooling and instructions for setting up a local development environment.
Get the code
-
Download the starting point for this tutorial via Git:
code language-shell $ git clone git@github.com:adobe/aem-guides-wknd-spa.git $ cd aem-guides-wknd-spa $ git checkout Angular/custom-component-start
-
Deploy the code base to a local AEM instance using Maven:
code language-shell $ mvn clean install -PautoInstallSinglePackage
If using AEM 6.x add the
classic
profile:code language-shell $ mvn clean install -PautoInstallSinglePackage -Pclassic
-
Install the finished package for the traditional WKND reference site. The images provided by WKND reference site are reused on the WKND SPA. The package can be installed using AEM’s Package Manager.
You can always view the finished code on GitHub or check out the code locally by switching to the branch Angular/custom-component-solution
.
Define the AEM Component
An AEM component is defined as a node and properties. In the project, these nodes and properties are represented as XML files in the ui.apps
module. Next, create the AEM component in the ui.apps
module.
-
Open the
ui.apps
folder in the IDE of your choice. -
Navigate to
ui.apps/src/main/content/jcr_root/apps/wknd-spa-angular/components
and create a folder namedcustom-component
. -
Create a file named
.content.xml
beneath thecustom-component
folder. Populate thecustom-component/.content.xml
with the following:code language-xml <?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" jcr:primaryType="cq:Component" jcr:title="Custom Component" componentGroup="WKND SPA Angular - Content"/>
jcr:primaryType="cq:Component"
- identifies that this node is an AEM component.jcr:title
is the value that is displayed to Content Authors and thecomponentGroup
determines the grouping of components in the authoring UI. -
Beneath the
custom-component
folder, create another folder named_cq_dialog
. -
Beneath the
_cq_dialog
folder create a file named.content.xml
and populate it with the following:code language-xml <?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primaryType="nt:unstructured" jcr:title="Custom Component" sling:resourceType="cq/gui/components/authoring/dialog"> <content jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/container"> <items jcr:primaryType="nt:unstructured"> <tabs jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/tabs" maximized="{Boolean}true"> <items jcr:primaryType="nt:unstructured"> <properties jcr:primaryType="nt:unstructured" jcr:title="Properties" sling:resourceType="granite/ui/components/coral/foundation/container" margin="{Boolean}true"> <items jcr:primaryType="nt:unstructured"> <columns jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns" margin="{Boolean}true"> <items jcr:primaryType="nt:unstructured"> <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/container"> <items jcr:primaryType="nt:unstructured"> <message jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/textfield" fieldDescription="The text to display on the component." fieldLabel="Message" name="./message"/> </items> </column> </items> </columns> </items> </properties> </items> </tabs> </items> </content> </jcr:root>
The above XML file generates a simple dialog for the
Custom Component
. The critical part of the file is the inner<message>
node. This dialog contains a simpletextfield
namedMessage
and persist the value of the textifeld to a property namedmessage
.A Sling Model is created next to expose the value of the
message
property via the JSON model.note note NOTE You can view a lot more examples of dialogs by viewing the Core Component definitions. You can also view additional form fields, like select
,textarea
,pathfield
, available beneath/libs/granite/ui/components/coral/foundation/form
in CRXDE-Lite.With a traditional AEM component, an HTL script is typically required. Since the SPA renders the component, no HTL script is needed.
Create the Sling Model
Sling Models are annotation driven Java™ “POJOs” (Plain Old Java™ Objects) that facilitate the mapping of data from the JCR to Java™ variables. Sling Models typically function to encapsulate complex server-side business logic for AEM Components.
In the context of the SPA Editor, Sling Models expose a component’s content through the JSON model through a feature using the Sling Model Exporter.
-
In the IDE of your choice open the
core
module.CustomComponent.java
andCustomComponentImpl.java
have already been created and stubbed out as part of the chapter starter code.note note NOTE If using Visual Studio Code IDE, it may be helpful to install extensions for Java™. -
Open the Java™ interface
CustomComponent.java
atcore/src/main/java/com/adobe/aem/guides/wknd/spa/angular/core/models/CustomComponent.java
:This is the Java™ interface that is implemented by the Sling Model.
-
Update
CustomComponent.java
so that it extends theComponentExporter
interface:code language-java package com.adobe.aem.guides.wknd.spa.angular.core.models; import com.adobe.cq.export.json.ComponentExporter; public interface CustomComponent extends ComponentExporter { public String getMessage(); }
Implementing the
ComponentExporter
interface is a requirement for the Sling Model to be automatically picked up by the JSON model API.The
CustomComponent
interface includes a single getter methodgetMessage()
. This is the method that exposes the value of the author dialog through the JSON model. Only getter methods with empty parameters()
are exported in the JSON model. -
Open
CustomComponentImpl.java
atcore/src/main/java/com/adobe/aem/guides/wknd/spa/angular/core/models/impl/CustomComponentImpl.java
.This is the implementation of the
CustomComponent
interface. The@Model
annotation identifies the Java™ class as a Sling Model. The@Exporter
annotation enables the Java™ class to be serialized and exported through the Sling Model Exporter. -
Update the static variable
RESOURCE_TYPE
to point to the AEM componentwknd-spa-angular/components/custom-component
created in the previous exercise.code language-java static final String RESOURCE_TYPE = "wknd-spa-angular/components/custom-component";
The resource type of the component is what binds the Sling Model to the AEM component and ultimately maps to the Angular component.
-
Add the
getExportedType()
method to theCustomComponentImpl
class to return the component resource type:code language-java @Override public String getExportedType() { return CustomComponentImpl.RESOURCE_TYPE; }
This method is required when implementing the
ComponentExporter
interface and exposes the resource type which allows the mapping to the Angular component. -
Update the
getMessage()
method to return the value of themessage
property persisted by the author dialog. Use the@ValueMap
annotation is map the JCR valuemessage
to a Java™ variable:code language-java import org.apache.commons.lang3.StringUtils; ... @ValueMapValue private String message; @Override public String getMessage() { return StringUtils.isNotBlank(message) ? message.toUpperCase() : null; }
Some additional “business logic” is added to return the value of message as upper case. This allows us to see the difference between the raw value stored by the author dialog and the value exposed by the Sling Model.
note note NOTE You can view the finished CustomComponentImpl.java here.
Update the Angular Component
The Angular code for the Custom Component has already been created. Next, make a few updates to map the Angular component to the AEM component.
-
In the
ui.frontend
module open the fileui.frontend/src/app/components/custom/custom.component.ts
-
Observe the
@Input() message: string;
line. It is expected that the transformed uppercase value is mapped to this variable. -
Import the
MapTo
object from the AEM SPA Editor JS SDK and use it to map to the AEM component:code language-diff + import {MapTo} from '@adobe/cq-angular-editable-components'; ... export class CustomComponent implements OnInit { ... } + MapTo('wknd-spa-angular/components/custom-component')(CustomComponent, CustomEditConfig);
-
Open
cutom.component.html
and observe that the value of{{message}}
is displayed in side an<h2>
tag. -
Open
custom.component.css
and add the following rule:code language-css :host-context { display: block; }
For the AEM Editor Placeholder to display properly when the component is empty the
:host-context
or another<div>
needs to be set todisplay: block;
. -
Deploy the updates to a local AEM environment from the root of the project directory, using your Maven skills:
code language-shell $ cd aem-guides-wknd-spa $ mvn clean install -PautoInstallSinglePackage
Update the Template Policy
Next, navigate to AEM to verify the updates and allow the Custom Component
to be added to the SPA.
-
Verify the registration of the new Sling Model by navigating to http://localhost:4502/system/console/status-slingmodels.
code language-plain com.adobe.aem.guides.wknd.spa.angular.core.models.impl.CustomComponentImpl - wknd-spa-angular/components/custom-component com.adobe.aem.guides.wknd.spa.angular.core.models.impl.CustomComponentImpl exports 'wknd-spa-angular/components/custom-component' with selector 'model' and extension '[Ljava.lang.String;@6fb4a693' with exporter 'jackson'
You should see the above two lines that indicate the
CustomComponentImpl
is associated with thewknd-spa-angular/components/custom-component
component and that it is registered via the Sling Model Exporter. -
Navigate to the SPA Page Template at http://localhost:4502/editor.html/conf/wknd-spa-angular/settings/wcm/templates/spa-page-template/structure.html.
-
Update the Layout Container’s policy to add the new
Custom Component
as an allowed component:Save the changes to the policy, and observe the
Custom Component
as an allowed component:
Author the Custom Component
Next, author the Custom Component
using the AEM SPA Editor.
-
Navigate to http://localhost:4502/editor.html/content/wknd-spa-angular/us/en/home.html.
-
In
Edit
mode, add theCustom Component
to theLayout Container
: -
Open the component’s dialog and enter a message that contains some lowercase letters.
This is the dialog that was created based on the XML file earlier in the chapter.
-
Save the changes. Observe that the message displayed is in all capitalized.
-
View the JSON model by navigating to http://localhost:4502/content/wknd-spa-angular/us/en.model.json. Search for
wknd-spa-angular/components/custom-component
:code language-json "custom_component_208183317": { "message": "HELLO WORLD", ":type": "wknd-spa-angular/components/custom-component" }
Notice that the JSON value is set to all capital letters based on the logic added to the Sling Model.
Congratulations! congratulations
Congratulations, you learned how to create a custom AEM component and how Sling Models and dialogs work with the JSON model.
You can always view the finished code on GitHub or check out the code locally by switching to the branch Angular/custom-component-solution
.
Next Steps next-steps
Extend a Core Component - Learn how to extend an existing Core Component to be used with the AEM SPA Editor. Understanding how to add properties and content to an existing component is a powerful technique to expand the capabilities of an AEM SPA Editor implementation.