Extending an AEM Screens Component
The following tutorial walks through the steps and best practices for extending out of the box AEM Screens components. The Image component is extended to add an authorable text overlay.
Overview overview
This tutorial is intended for developers who are new to AEM Screens. In this tutorial, the Screens Image component is extended to create a Poster component. A title, description, and logo are overlaid on top of an image to create a compelling experience in a Sequence channel.
A Custom Poster
component is created by extending the Image component.
Prerequisites prerequisites
To complete this tutorial, you need the following:
- AEM 6.5 + Latest Screens Feature Pack
- AEM Screens Player
- Local Development Environment
The tutorial steps and screenshots are performed using CRXDE-Lite. Eclipse or IntelliJ 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 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 manage
http://localhost:4502/crx/packmgr/index.jsp)r:
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
SRC Start Screens
We.Retail
Run Project -
In CRX Package Manager
http://localhost:4502/crx/packmgr/index.jsp
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
AEM Screens
We.Retail Run Ui.Apps
andUi.Content
packages installed by way of CRX Package Manager
Create the Poster component poster-cmp
The Poster component extends the out-of-the-box AEM Screens Image component. A mechanism of Sling, sling:resourceSuperType
, is used to inherit the core functionality of the Image component without having to copy and paste. More information about the basics of Sling Request Processing can be found here.
The Poster component is rendered in full screen in preview/production mode. In edit mode, it is important to render the component differently to facilitate authoring the Sequence channel.
-
In CRXDE-Lite
http://localhost:4502/crx/de/index.jsp
(or IDE of choice) beneath to/apps/weretail-run/components/content
create acq:Component
namedposter
.Add the following properties to the
poster
component: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" jcr:primaryType="cq:Component" jcr:title="Poster" sling:resourceSuperType="screens/core/components/content/image" componentGroup="We.Retail Run - Content"/>
Properties for /apps/weretail-run/components/content/poster
By setting the
sling:resourceSuperType
property equal toscreens/core/components/content/image
, the Poster component effectively inherits all functionality of the Image component. Equivalent nodes and files found beneathscreens/core/components/content/image
can be added beneath theposter
component to override and extend the functionality. -
Copy the
cq:editConfig
node beneath/libs/screens/core/components/content/image
. Paste thecq:editConfig
beneath the/apps/weretail-run/components/content/poster
component.On the
cq:editConfig/cq:dropTargets/image/parameters
node, update thesling:resourceType
property to equalweretail-run/components/content/poster
.XML representation of the
cq:editConfig
represented below: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="cq:EditConfig"> <cq:dropTargets jcr:primaryType="nt:unstructured"> <image jcr:primaryType="cq:DropTargetConfig" accept="[image/.*]" groups="[media]" propertyName="./fileReference"> <parameters jcr:primaryType="nt:unstructured" sling:resourceType="weretail-run/components/content/poster" imageCrop="" imageMap="" imageRotate=""/> </image> </cq:dropTargets> </jcr:root>
-
Copy WCM Foundation
image
dialog to be used for theposter
component.It is easiest to start from an existing dialog and then make modifications.
- Copy the dialog from:
/libs/wcm/foundation/components/image/cq:dialog
- Paste the dialog beneath
/apps/weretail-run/components/content/poster
Copied dialog from
/libs/wcm/foundation/components/image/cq:dialog
to/apps/weretail-run/components/content/poster
The AEM Screens
image
component is supertyped to the WCM Foundationimage
component. Therefore, theposter
component inherits functionality from both. The dialog for the poster component is made up of a combination of the Screens and Foundation dialogs. Features of the Sling Resource Merger are used to hide irrelevant dialog fields and tabs that are inherited from the supertyped components. - Copy the dialog from:
-
Update the
cq:dialog
beneath/apps/weretail-run/components/content/poster
with the following changes represented 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="Poster" sling:resourceType="cq/gui/components/authoring/dialog"> <content jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/container"> <layout jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/layouts/tabs" type="nav"/> <items jcr:primaryType="nt:unstructured"> <image jcr:primaryType="nt:unstructured" jcr:title="Elements" sling:resourceType="granite/ui/components/foundation/section"> <layout jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns" margin="{Boolean}false"/> <items jcr:primaryType="nt:unstructured"> <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/container"> <items jcr:primaryType="nt:unstructured" sling:hideChildren="[linkURL,size]"> <file jcr:primaryType="nt:unstructured" sling:resourceType="cq/gui/components/authoring/dialog/fileupload" autoStart="{Boolean}false" class="cq-droptarget" fieldLabel="Image asset" fileNameParameter="./fileName" fileReferenceParameter="./fileReference" mimeTypes="[image]" multiple="{Boolean}false" name="./file" title="Upload Image Asset" uploadUrl="${suffix.path}" useHTML5="{Boolean}true"/> <title jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/textfield" fieldLabel="Title" name="./jcr:title"/> <description jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/textarea" fieldLabel="Description" name="./jcr:description"/> <position jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/click" fieldLabel="Text Position" name="./textPosition"> <items jcr:primaryType="nt:unstructured"> <left jcr:primaryType="nt:unstructured" text="Left" value="left"/> <center jcr:primaryType="nt:unstructured" text="Center" value="center"/> <right jcr:primaryType="nt:unstructured" text="Right" value="right"/> </items> </position> <color jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/click" fieldLabel="Text Color" name="./textColor"> <items jcr:primaryType="nt:unstructured"> <light jcr:primaryType="nt:unstructured" text="Light" value="light"/> <dark jcr:primaryType="nt:unstructured" text="Dark" value="dark"/> </items> </color> </items> </column> </items> </image> <accessibility jcr:primaryType="nt:unstructured" sling:hideResource="{Boolean}true"/> </items> </content> </jcr:root>
The property
sling:hideChildren
="[linkURL,size]
" is used on theitems
node to ensure that the linkURL and size fields are hidden from the dialog. Removing these nodes from the poster dialog is not enough. The propertysling:hideResource="{Boolean}true"
on the accessibility tab is used to hide the entire tab.Two click fields are added to the dialog box, Text Position and Text Color, to give authors control over the position of the text and color of the Title and Description.
Poster - Final Dialog Structure
At this point, an instance of the
poster
component can be added to the Idle Channel page in theWe.Retail
Run project:http://localhost:4502/editor.html/content/screens/we-retail-run/channels/idle-channel.edit.html
.Poster Dialog fields
-
Create a file beneath
/apps/weretail-run/components/content/poster
namedproduction.html.
Populate the file with the following:
code language-xml <!--/* /apps/weretail-run/components/content/poster/production.html */--> <div data-sly-use.image="image.js" data-duration="${properties.duration}" class="cmp-poster" style="background-image: url(${request.contextPath @ context='uri'}${image.src @ context='uri'});"> <div class="cmp-poster__text cmp-poster__text--${properties.textPosition @ context='attribute'} cmp-poster__text--${properties.textColor @ context='attribute'}"> <h1 class="cmp-poster__title">${properties.jcr:title}</h1> <h2 class="cmp-poster__description">${properties.jcr:description}</h2> </div> <img class="cmp-poster__logo" src="/content/dam/we-retail-run/logos/we-retail-run_dark.png" alt="we-retail-logo" /> </div>
The production markup for the Poster component is seen directly above. The HTL script overrides
screens/core/components/content/image/production.html
. Theimage.js
is a server-side script that creates a POJO-like Image object. The Image object can then be called to render thesrc
as an inline style background-image.The h1
and h2 tags are added display the Title and Description based on the component properties:${properties.jcr:title}
and${properties.jcr:description}
.Surrounding the
h1
andh2
tags is a div wrapper with three CSS classes with variations of “cmp-poster__text
.” The value for thetextPosition
andtextColor
properties are used to change the CSS class rendered based on the dialog selection of the author. In the next section CSS from client libraries are written to enable these changes in display.A logo is also included as an overlay in the component. In this example, the path to the
We.Retail
logo is hard-coded in the DAM. Depending on the use case, it might make more sense to create a dialog field to make the logo path a dynamically populated value.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/poster
namededit.html.
Populate the file with the following:
code language-xml <!--/* /apps/weretail-run/components/content/poster/edit.html */--> <div class="aem-Screens-editWrapper ${image.cssClass} cmp-poster" data-sly-use.image="image.js" data-emptytext="${'Poster' @ i18n, locale=request.locale}"> <img class="cmp-poster__image" src="${request.contextPath}${image.src @ context='uri'}" width="100%" /> <div class="cmp-poster__text cmp-poster__text--${properties.textPosition @ context='attribute'} cmp-poster__text--${properties.textColor @ context='attribute'}"> <p class="cmp-poster__title">${properties.jcr:title}</p> <p class="cmp-poster__description">${properties.jcr:description}</p> </div> </div>
The edit markup for the Poster component is seen directly above. The HTL script overrides
/libs/screens/core/components/content/image/edit.html
. The markup is similar to theproduction.html
markup and displays the title and description on top of the image.The
aem-Screens-editWrapper
is added so that the component is not rendered full-screen in the editor. Thedata-emptytext
attribute ensures that a placeholder is displayed when no image or content has been populated.
Create Client-Side Libraries clientlibs
Client-Side Libraries provide a mechanism to organize and manage CSS and JavaScript files necessary for an AEM implementation. More information about using Client-Side Libraries can be found here.
AEM Screens components are rendered differently in Edit mode vs. Preview/Production mode. Two sets of client libraries are created, one for Edit mode and a second for Preview/Production.
-
Create a folder for client-side libraries for the Poster component.
Beneath
/apps/weretail-run/components/content/poster
, create a folder namedclientlibs
. -
Beneath the
clientlibs
folder, create a 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/poster/clientlibs/shared
The
categories
property is a string that identifies the client library. Thecq.screens.components
category is used in both Edit and Preview/Production mode. Therefore, any CSS/JS defined in theshared
clientlib is loaded in all modes.As a best practice, never expose any paths directly to
/apps
in a production environment. TheallowProxy
property ensures the client library CSS and JS is referenced through a prefix of/etc.clientlibs
. More information about the allowProxy property can be found here. -
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 must be compiled outside of AEM.
-
Populate
/apps/weretail-run/components/content/poster/clientlibs/shared/css/styles.less
with the following:code language-css /* /apps/weretail-run/components/content/poster/clientlibs/shared/css/styles.less Poster component - Shared Style */ @import url('https://fonts.googleapis.com/css?family=Fjalla+One|Open+Sans:400i'); @text-light-color: #fff; @text-dark-color: #000; @title-font-family: 'Fjalla One', sans-serif; @description-font-family: 'Open Sans', sans-serif; .cmp-poster { &__text { position: absolute; color: @text-light-color; top: 0; text-align:center; width: 100%; &--left { text-align: left; margin-left: 1em; } &--right { text-align: right; margin-right: 1em; } &--dark { color: @text-dark-color; } } &__title { font-weight: bold; font-family: @title-font-family; font-size: 1.2em; } &__description { font-style: italic; font-family: @description-font-family; } }
note note NOTE Google Web Fonts are used for the font families. Web Fonts require Internet connectivity and not all AEM Screens implementations have a reliable connection. Planning for offline mode is an important consideration for AEM Screens deployments. -
Copy the
shared
client library folder. Paste it as a sibling and rename it toproduction
. -
Update the
categories
property of the production client library to becq.screens.components.production.
The
cq.screens.components.production
category ensures that the styles are only loaded when in Preview/Production mode.Properties for /apps/weretail-run/components/content/poster/clientlibs/production
-
Populate
/apps/weretail-run/components/content/poster/clientlibs/production/css/styles.less
with the following:code language-css /* /apps/weretail-run/components/content/poster/clientlibs/production/css/styles.less Poster component - Production Style */ .cmp-poster { background-size: cover; height: 100%; width: 100%; position:absolute; &__text { top: 2em; &--left { width: 40%; top: 5em; } &--right { width: 40%; right: 1em; } } &__title { font-size: 5rem; font-weight: 900; margin: 0.1rem; } &__description { font-size: 2rem; margin: 0.1rem; font-weight: 400; } &__logo { position: absolute; max-width: 200px; top: 1em; left: 0; } }
The above styles display the Title and Description in an absolute position on the screen. The title is displayed larger than the description. The BEM notation of the component makes it easy to carefully scope the styles within the cmp-poster class.
A third client library 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
Add Poster component to a Sequence channel add-sequence-channel
The Poster component is used on a Sequence channel. The starter package for this tutorial included an Idle channel. The Idle channel is pre-configured to allow components of the group We.Retail Run - Content
. The Poster component’s group is set to We.Retail Run - Content
and is available to be added to the channel.
-
Open the Idle channel from the
We.Retail
Run project:http://localhost:4502/editor.html/content/screens/we-retail-run/channels/idle-channel.edit.html
-
Drag + Drop a new instance of the Poster component from the side bar on to the page.
-
Edit the dialog box of the Poster component so you can add an Image, Title, Description. Use the Text Position and Text Color choices to ensure the Title/Description is readable over the Image.
-
To add a few Poster components, repeat the steps above. Add transitions in between the components.
Putting it all together 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.
Finished Code 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 uncompiled source code that can be deployed using Maven.
SRC Final AEM Screens We.Retail
Run Project