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.

overviewhellow

Prerequisites

To complete this tutorial the following is needed:

  1. AEM 6.5 or AEM 6.3 + Latest Screens Feature Pack

  2. AEM Screens Player

  3. 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.

  1. Download and install the following packages using CRX package manager:

    Get File

    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

    Get File

  2. In CRX Package Manager verify the following two packages are installed:

    1. screens-weretail-run.ui.content-0.0.1-SNAPSHOT.zip
    2. 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

    Screens We.Retail Run Ui.Apps and Ui.Content packages installed via CRX Package Manager

  3. 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

    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.

  4. 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.

  5. 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.

    we-retaiul-run-starter

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
  1. 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

    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.

  2. Create a file beneath /apps/weretail-run/components/content/helloworld named helloworld.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:

    1. Production: Preview or Publish mode (wcmmode=disabled)
    2. Edit: used for all other authoring modes, i.e edit, design, scaffolding, developer…

    helloworld.htmlacts 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 an edit.html script for Edit mode and a production.html script for Production mode.

  3. Create a file beneath /apps/weretail-run/components/content/helloworld named production.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. The data-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 an h1 tag with text. ${properties.message} is a portion of HTL script that will output the contents of a JCR property named message. A dialog is created later that allows a user to enter a value for the message 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.

  4. Create a file beneath /apps/weretail-run/components/content/helloworld named edit.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 and data-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.

  5. 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.

    1. Copy the dialog from: /libs/screens/core/components/content/image/cq:dialog
    2. Paste the dialog beneath /apps/weretail-run/components/content/helloworld

    copy-image-dialog

  6. 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 named duration. 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

    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.

  1. Create a folder for client-side libraries for the Hello World component.

    Beneath /apps/weretail-run/components/content/helloworldcreate a new folder named clientlibs.

    2018-04-30_at_1046am

  2. Beneath the clientlibs folder create a new node named shared of type cq:ClientLibraryFolder.

    2018-04-30_at_1115am

  3. 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

    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.

  4. Create file named css.txt beneath the shared folder.

    Populate the file with the following:

    code language-none
    #base=css
    
    styles.less
    
  5. Create a folder named css beneath the shared folder. Add a file named style.less beneath the css folder. The structure of the client libraries should now look like this:

    2018-04-30_at_3_11pm

    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.

  6. 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;
     }
    }
    
  7. Copy and paste the shared client library folder to create a new client library named production.

    Copy the shared client library to create a new production client library

    Copy the shared client library to create a new production client library

  8. Update the categories property of the production clientlibrary to be cq.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

    Properties for /apps/weretail-run/components/content/helloworld/clientlibs/production

  9. 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.

Clientlib Category
Usage
cq.screens.components
Styles and scripts that are shared between both edit and production modes
cq.screens.components.edit
Styles and scripts that are only used in edit mode
cq.screens.components.production
Styles and scripts that are only used in production mode

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.

  1. In CRXDE-Lite http://localhost:4502/crx/de/index.jsp#/apps/settings/wcm/designs navigate to /apps/settings/wcm/designs

  2. Create a new node beneath the designs folder, named we-retail-run with a type of cq:Page.

  3. Beneath the we-retail-run page, add another node named jcr:content of type nt:unstructured. Add the following properties to the jcr: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

    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.

  1. From the AEM Start Menu navigate to Screens > We.Retail Ru n > and select Channels.

  2. Click the Create button

    1. Choose Create Entity

    2018-04-30_at_5_18pm

  3. In the Create wizard:

  4. Template Step - choose Sequence Channel

    1. Properties Step

    • Basic Tab > Title = Idle Channel
    • Channel Tab > check Make channel online

    idle-channel

  5. 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 /apps/settings/wcm/designs/we-retail-run

    Design config pointing to /apps/settings/wcm/designs/we-retail-run

  6. Edit the newly created Idle Channel to open it.

  7. Switch the page mode to Design Mode

    1. Click the wrench Icon in the Parsys to configure the allowed components

    2. Select the Screens group and the We.Retail Run - Content group.

    2018-04-30_at_5_43pm

  8. Switch the page mode to Edit. The Hello World component can now be added to the page and combined with other sequence channel components.

    2018-04-30_at_5_53pm

  9. 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 the components property now includes group:Screens, group:We.Retail Run - Content.

    Design configuration under /apps/settings/wcm/designs/we-retail-run

    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:

  1. Instead of directly extending foundation/components/parbase, you’d have to extend either screens/core/components/content/page or screens/core/components/content/experiencefragment
  2. 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.

Get File

Get File

Get File

adce462a-f916-4dbe-9ab5-0b62cfb0f053