Developing a Custom Component for AEM Screens
The following tutorial walks through the steps to create a custom component for AEM Screens. AEM Screens reuses many existing design patterns and technologies of other AEM products. The tutorial highlights differences and special considerations when developing for AEM Screens.
Overview
This tutorial is intended for developers who are new to AEM Screens. In this tutorial a simple “Hello World” component is built for a Sequence channel in AEM Screens. A dialog allows authors to update the text displayed.
Prerequisites
To complete this tutorial the following is needed:
-
Local Development Environment
The tutorial steps and screen shots are performed using CRXDE-Lite. IDEs can also be used to complete the tutorial. More information on using an IDE to develop with AEM can be found here.
Project Setup
A Screens project’s source code is typically managed as a multi-module Maven project. To expedite the tutorial, a project was pre-generated using the AEM Project Archetype 13. More details on creating a project with Maven AEM Project Archetype can be found here.
-
Download and install the following packages using CRX package manager:
Get File
Optionally if working with Eclipse or another IDE download the below source package. Deploy the project to a local AEM instance by using the Maven command:mvn -PautoInstallPackage clean install
Start HelloWorld SRC Screens We.Retail Run Project
-
In CRX Package Manager verify the following two packages are installed:
- screens-weretail-run.ui.content-0.0.1-SNAPSHOT.zip
- screens-weretail-run.ui.apps-0.0.1-SNAPSHOT.zip
Screens We.Retail Run Ui.Apps and Ui.Content packages installed via CRX Package Manager
-
The screens-weretail-run.ui.apps package installs code beneath
/apps/weretail-run
.This package contains the code responsible for rendering custom components for the project. This package includes component code and any JavaScript or CSS needed. This package also embeds screens-weretail-run.core-0.0.1-SNAPSHOT.jar which contains any Java code needed by the project.
note note NOTE In this tutorial no Java code is written. If more complex business logic is needed, back-end Java can be created and deployed using the Core Java bundle. Representation of the ui.apps code in CRXDE Lite
The helloworld component is currently just a placeholder. Over the course of the tutorial, functionality will be added allowing an author to update the message displayed by the component.
-
The screens-weretail-run.ui.content package installs code beneath:
/conf/we-retail-run
/content/dam/we-retail-run
/content/screens/we-retail-run
This package contains the starting content and configuration structure needed for the project.
/conf/we-retail-run
contains all configurations for the We.Retail Run project./content/dam/we-retail-run
includes starting digital assets for the project./content/screens/we-retail-run
contains the Screens content structure. The content beneath all of these paths is updated primarily in AEM. To promote consistency between environments (local, Dev, Stage, Prod) often a base content structure is saved in source control. -
Navigate to the AEM Screens > We.Retail Run project:
From the AEM Start Menu > Click the Screens the icon. Verify the We.Retail Run Project can be seen.
Create the Hello World Component
The Hello World component is a simple component that allows a user to input a message to be displayed on the screen. The component is based on the AEM Screens Component Template: https://github.com/Adobe-Marketing-Cloud/aem-screens-component-template.
AEM Screens has some interesting constraints that are not necessarily true for traditional WCM Sites components.
- Most Screens components need to run in full screen on the target digital signage devices
- Most Screens components need to be embeddable in the sequence channels to generate slideshows
- Authoring should allow editing individual components in a sequence channel, so rendering them fullscreen is out of the question
-
In CRXDE-Lite
http://localhost:4502/crx/de/index.jsp
(or IDE of choice) navigate to/apps/weretail-run/components/content/helloworld.
Add the following properties to the
helloworld
component:code language-none jcr:title="Hello World" sling:resourceSuperType="foundation/components/parbase" componentGroup="We.Retail Run - Content"
Properties for /apps/weretail-run/components/content/helloworld
The helloworld component extends the foundation/components/parbase component so it can properly be used inside a sequence channel.
-
Create a file beneath
/apps/weretail-run/components/content/helloworld
namedhelloworld.html.
Populate the file with the following:
code language-xml <!--/* /apps/weretail-run/components/content/helloworld/helloworld.html */--> <!--/* production: preview authoring mode + unspecified mode (i.e. on publish) */--> <sly data-sly-test.production="${wcmmode.preview || wcmmode.disabled}" data-sly-include="production.html" /> <!--/* edit: any other authoring mode, i.e. edit, design, scaffolding, etc. */--> <sly data-sly-test="${!production}" data-sly-include="edit.html" />
Screens components require two different renderings depending on which authoring mode is being used:
- Production: Preview or Publish mode (wcmmode=disabled)
- Edit: used for all other authoring modes, i.e edit, design, scaffolding, developer…
helloworld.html
acts as a switch, checking which authoring mode is currently active and redirecting to to another HTL script. A common convention used by screens components is to have anedit.html
script for Edit mode and aproduction.html
script for Production mode. -
Create a file beneath
/apps/weretail-run/components/content/helloworld
namedproduction.html.
Populate the file with the following:
code language-xml <!--/* /apps/weretail-run/components/content/helloworld/production.html */--> <div data-duration="${properties.duration}" class="cmp-hello-world"> <h1 class="cmp-hello-world__message">${properties.message}</h1> </div>
Above is the production markup for the Hello World Component. A
data-duration
attribute is included since the component is used on a Sequence channel. Thedata-duration
attribute is used by the sequence channel to know for how long a sequence item is to be displayed.The component renders a
div
and anh1
tag with text.${properties.message}
is a portion of HTL script that will output the contents of a JCR property namedmessage
. A dialog is created later that allows a user to enter a value for themessage
property text.Also note that BEM (Block Element Modifier) notation is used with the component. BEM is a CSS coding convention that makes it easier to create reusable components. BEM is the notation used by AEM’s Core Components.
-
Create a file beneath
/apps/weretail-run/components/content/helloworld
namededit.html.
Populate the file with the following:
code language-xml <!--/* /apps/weretail-run/components/content/helloworld/edit.html */--> <!--/* if message populated */--> <div data-sly-test.message="${properties.message}" class="aem-Screens-editWrapper cmp-hello-world"> <p class="cmp-hello-world__message">${message}</p> </div> <!--/* empty place holder */--> <div data-sly-test="${!message}" class="aem-Screens-editWrapper cq-placeholder cmp-hello-world" data-emptytext="${'Hello World' @ i18n, locale=request.locale}"> </div>
Above is the edit markup for the Hello World Component. The first block displays an edit version of the component if the dialog message has been populated.
The second block is rendered if no dialog message has been entered. The
cq-placeholder
anddata-emptytext
render the label Hello World as a place holder in that case. The string for the label can be internationalized using i18n in order to support authoring in multiple locales. -
Copy Screens Image Dialog to be used for the Hello World component.
It is easiest to start from an existing dialog and then make modifications.
- Copy the dialog from:
/libs/screens/core/components/content/image/cq:dialog
- Paste the dialog beneath
/apps/weretail-run/components/content/helloworld
- Copy the dialog from:
-
Update Hello World dialog to include a tab for message.
Update the dialog so that it matches the following. The JCR node structure of the final dialog is presented below in XML:
code language-xml <?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="https://sling.apache.org/jcr/sling/1.0" xmlns:cq="https://www.day.com/jcr/cq/1.0" xmlns:jcr="https://www.jcp.org/jcr/1.0" xmlns:nt="https://www.jcp.org/jcr/nt/1.0" jcr:primaryType="nt:unstructured" jcr:title="Hello World" sling:resourceType="cq/gui/components/authoring/dialog"> <content jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/tabs" size="L"> <items jcr:primaryType="nt:unstructured"> <message jcr:primaryType="nt:unstructured" jcr:title="Message" sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"> <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="Message for component to display" fieldLabel="Message" name="./message"/> </items> </column> </items> </message> <sequence jcr:primaryType="nt:unstructured" jcr:title="Sequence" sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"> <items jcr:primaryType="nt:unstructured"> <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/container"> <items jcr:primaryType="nt:unstructured"> <duration jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/numberfield" defaultValue="" fieldDescription="Amount of time the image will be shown in the sequence, in milliseconds" fieldLabel="Duration (ms)" min="0" name="./duration"/> </items> </column> </items> </sequence> </items> </content> </jcr:root>
The textfield for the Message will be saved to a property named
message
and that the numberfield for the Duration will be saved to a property namedduration
. These two properties are both referenced in/apps/weretail-run/components/content/helloworld/production.html
by HTL as${properties.message}
and${properties.duration}
.Hello World - completed dialog
Create Client-Side Libraries
Client-Side Libraries provide a mechanism to organize and manage CSS and JavaScript files necessary for an AEM implementation.
AEM Screens components are rendered differently in Edit mode vs. Preview/Production mode. Two client libraries will be created, one for Edit mode and a second for Preview/Production.
-
Create a folder for client-side libraries for the Hello World component.
Beneath
/apps/weretail-run/components/content/helloworld
create a new folder namedclientlibs
. -
Beneath the
clientlibs
folder create a new node namedshared
of typecq:ClientLibraryFolder.
-
Add the following properties to the shared client library:
-
allowProxy
| Boolean |true
-
categories
| String[] |cq.screens.components
Properties for /apps/weretail-run/components/content/helloworld/clientlibs/shared
The categories property is a string that identifies the client library. The cq.screens.componentscategory is used in both Edit and Preview/Production mode. Therefore any CSS/JS defined in the sharedclientlib is loaded in all modes.
It is a best practice to never expose any paths directly to /apps in a production environment. The allowProxy property ensures the client library CSS and JS is referenced via a prefix of/etc.clientlibs.
-
-
Create file named
css.txt
beneath the shared folder.Populate the file with the following:
code language-none #base=css styles.less
-
Create a folder named
css
beneath theshared
folder. Add a file namedstyle.less
beneath thecss
folder. The structure of the client libraries should now look like this:Instead of writing CSS directly, this tutorial uses LESS. LESS is a popular CSS pre-compiler that supports CSS variables, mixins, and functions. AEM client libraries natively support LESS compilation. Sass or other pre-compilers can be used but need to be compiled outside of AEM.
-
Populate
/apps/weretail-run/components/content/helloworld/clientlibs/shared/css/styles.less
with the following:code language-css /** Shared Styles /apps/weretail-run/components/content/helloworld/clientlibs/shared/css/styles.less **/ .cmp-hello-world { background-color: #fff; &__message { color: #000; font-family: Helvetica; text-align:center; } }
-
Copy and paste the
shared
client library folder to create a new client library namedproduction
.Copy the shared client library to create a new production client library
-
Update the
categories
property of the production clientlibrary to becq.screens.components.production.
This ensures that the styles are only loaded when in Preview/Production mode.
Properties for /apps/weretail-run/components/content/helloworld/clientlibs/production
-
Populate
/apps/weretail-run/components/content/helloworld/clientlibs/production/css/styles.less
with the following:code language-css /** Production Styles /apps/weretail-run/components/content/helloworld/clientlibs/production/css/styles.less **/ .cmp-hello-world { height: 100%; width: 100%; position: fixed; &__message { position: relative; font-size: 5rem; top:25%; } }
The above styles will display the message centered in the middle of the screen, but only in production mode.
A third clientlibrary category: cq.screens.components.edit
could be used to add Edit-only specific styles to the component.
cq.screens.components
cq.screens.components.edit
cq.screens.components.production
Create a Design Page
AEM Screens uses static Page Templates and Design configurations for global changes. Design configurations are frequently used to configure allowed components for the Parsys on a channel. A best practice is to store these configurations in an app-specific way.
Below a We.Retail Run Design page is created that will store all configurations specific to the We.Retail Run project.
-
In CRXDE-Lite
http://localhost:4502/crx/de/index.jsp#/apps/settings/wcm/designs
navigate to/apps/settings/wcm/designs
-
Create a new node beneath the designs folder, named
we-retail-run
with a type ofcq:Page
. -
Beneath the
we-retail-run
page, add another node namedjcr:content
of typent:unstructured
. Add the following properties to thejcr:content
node:table 0-row-3 1-row-3 2-row-3 3-row-3 Name Type Value jcr:title String We.Retail Run sling:resourceType String wcm/core/components/designer cq:doctype String html_5 Design Page at /apps/settings/wcm/designs/we-retail-run
Create a Sequence Channel
The Hello World component is intended to be used on a Sequence Channel. To test the component, a new Sequence Channel is created.
-
From the AEM Start Menu navigate to Screens > We.Retail Ru n > and select Channels.
-
Click the Create button
- Choose Create Entity
-
In the Create wizard:
-
Template Step - choose Sequence Channel
-
Properties Step
- Basic Tab > Title = Idle Channel
- Channel Tab > check Make channel online
-
-
Open the page properties for the Idle Channel. Update the Design field to point to
/apps/settings/wcm/designs/we-retail-run,
the design page created in the previous section.Design config pointing to /apps/settings/wcm/designs/we-retail-run
-
Edit the newly created Idle Channel to open it.
-
Switch the page mode to Design Mode
-
Click the wrench Icon in the Parsys to configure the allowed components
-
Select the Screens group and the We.Retail Run - Content group.
-
-
Switch the page mode to Edit. The Hello World component can now be added to the page and combined with other sequence channel components.
-
In CRXDE-Lite
http://localhost:4502/crx/de/index.jsp#/apps/settings/wcm/designs/we-retail-run/jcr%3Acontent/sequencechannel/par
navigate to/apps/settings/wcm/designs/we-retail-run/jcr:content/sequencechannel/par
. Notice thecomponents
property now includesgroup:Screens
,group:We.Retail Run - Content
.Design configuration under /apps/settings/wcm/designs/we-retail-run
Template for Custom Handlers
In case your custom component is using external resources such as assets (images, videos, fonts, icons, etc.), specific asset renditions or client-side libraries (css, js, etc.), then these are not automatically added to the offline configuration as we only bundle the HTML markup by default.
In order to let you customize and optimize the exact assets that are downloaded to the player, we offer an extension mechanism for custom components to expose their dependencies to the offline caching logic in Screens.
The section below showcases the template for custom offline resource handlers and the minimum requirements in the pom.xml
for that specific project.
package …;
import javax.annotation.Nonnull;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import com.adobe.cq.screens.visitor.OfflineResourceHandler;
@Service(value = OfflineResourceHandler.class)
@Component(immediate = true)
public class MyCustomHandler extends AbstractResourceHandler {
@Reference
private …; // OSGi services injection
/**
* The resource types that are handled by the handler.
* @return the handled resource types
*/
@Nonnull
@Override
public String[] getSupportedResourceTypes() {
return new String[] { … };
}
/**
* Accept the provided resource, visit and traverse it as needed.
* @param resource The resource to accept
*/
@Override
public void accept(@Nonnull Resource resource) {
ValueMap properties = ResourceUtil.getValueMap(resource);
/* You can directly add explicit paths for offline caching using the `visit`
method of the visitor. */
// retrieve a custom property from the component
String myCustomRenditionUrl = properties.get("myCustomRenditionUrl", String.class);
// adding that exact asset/rendition/path to the offline manifest
this.visitor.visit(myCustomRenditionUrl);
/* You can delegate handling for dependent resources so they are also added to
the offline cache using the `accept` method of the visitor. */
// retrieve a referenced dependent resource
String referencedResourcePath = properties.get("myOtherResource", String.class);
ResourceResolver resolver = resource.getResourceResolver();
Resource referencedResource = resolver.getResource(referencedResourcePath);
// let the handler for that resource handle it
if (referencedResource != null) {
this.visitor.accept(referencedResource);
}
}
}
The following code provides the minimum requirements in the pom.xml
for that specific project:
<dependencies>
…
<!-- Felix annotations -->
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
<version>1.9.0</version>
<scope>provided</scope>
</dependency>
<!-- Screens core bundle with OfflineResourceHandler/AbstractResourceHandler -->
<dependency>
<groupId>com.adobe.cq.screens</groupId>
<artifactId>com.adobe.cq.screens</artifactId>
<version>1.5.90</version>
<scope>provided</scope>
</dependency>
…
</dependencies>
Putting it all together
The below video shows the finished component and how it can be added to a Sequence channel. The Channel is then added to a Location display and ultimately assigned to a Screens player.
Additional considerations for custom components embedding other pages or fragments
If the custom component you are developing is meant to include other pages or experience fragments, and if you want changes in the embedded content to be automatically picked up by the player without having to republish the channel, you need to take these 2 constraints into consideration:
- Instead of directly extending
foundation/components/parbase
, you’d have to extend eitherscreens/core/components/content/page
orscreens/core/components/content/experiencefragment
- The name of the property you use to reference the embedded content needs to be
pagePath
Leveraging those 2 Screens core components also comes with the added benefit that they can take care of bundling some of the dependencies you need (client-side libraries, fonts, etc.) via their offline config options in the component dialog, which then reduces the responsibility of any custom offline handler you’d have to use for this, sometimes even completely removing the need to use one in the first place.
Finished Code
Below is the finished code from the tutorial. The screens-weretail-run.ui.apps-0.0.1-SNAPSHOT.zip and screens-weretail-run.ui.content-0.0.1-SNAPSHOT.zip are the compiled AEM packages. The **SRC-screens-weretail-run-0.0.1.zip **is the un-compiled source code that can be deployed using Maven.