*This article was modeled from the current Adobe React SPA documentation.
Learn how to extend an existing Core Component to be used with the AEM SPA Editor. Understanding how to extend an existing component is a powerful technique to customize and expand the capabilities of an AEM SPA Editor implementation.
Objective
- Extend an existing Core Component with additional properties and content.
- Understand the basic of Component Inheritance with the use of
sling:resourceSuperType. - Learn how to leverage the Delegation Pattern for Sling Models to re-use existing logic and functionality.
What you will build
This chapter illustrates the additional code needed to add an extra property to a standard Image component to fulfill the requirements for a new Banner component. The Banner component contains all of the same properties as the standard Image component but includes an additional property for users to populate the Banner Text.

Prerequisites
Review the required tooling and instructions for setting up a local development environment. It is assumed at this point in the tutorial users have a solid understanding of the AEM SPA Editor feature.
Inheritance with Sling Resource Super Type
To extend an existing component set a property named sling:resourceSuperType on your component’s definition. sling:resourceSuperTypeis a property that can be set on an AEM component’s definition that points to another component. This explicitly sets the component to inherit all functionality of the component identified as the sling:resourceSuperType.
If we want to extend the Image component at wknd-spa-vue/components/image we need to update the code in the ui.apps module.
- Create a new folder beneath the
ui.appsmodule forbanneratui.apps/src/main/content/jcr_root/apps/wknd-spa-vue/components/banner. - Beneath
bannercreate a Component definition (.content.xml) like the following:
<?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="Banner"
sling:resourceSuperType="wknd-spa-vue/components/image"
componentGroup="WKND SPA Vue - Content"/>
This sets wknd-spa-vue/components/banner to inherit all functionality of wknd-spa-vue/components/image.
cq:editConfig
The _cq_editConfig.xml file dictates the drag and drop behavior in the AEM authoring UI. When extending the Image component it is important that the resource type matches the component itself.
- In the
ui.appsmodule create another file beneathbannernamed_cq_editConfig.xml. - Populate
_cq_editConfig.xmlwith the following 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" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="cq:EditConfig">
<cq:dropTargets jcr:primaryType="nt:unstructured">
<image
jcr:primaryType="cq:DropTargetConfig"
accept="[image/gif,image/jpeg,image/png,image/webp,image/tiff,image/svg\\+xml]"
groups="[media]"
propertyName="./fileReference">
<parameters
jcr:primaryType="nt:unstructured"
sling:resourceType="wknd-spa-vue/components/banner"
imageCrop=""
imageMap=""
imageRotate=""/>
</image>
</cq:dropTargets>
<cq:inplaceEditing
jcr:primaryType="cq:InplaceEditingConfig"
active="{Boolean}true"
editorType="image">
<inplaceEditingConfig jcr:primaryType="nt:unstructured">
<plugins jcr:primaryType="nt:unstructured">
<crop
jcr:primaryType="nt:unstructured"
supportedMimeTypes="[image/jpeg,image/png,image/webp,image/tiff]"
features="*">
<aspectRatios jcr:primaryType="nt:unstructured">
<wideLandscape
jcr:primaryType="nt:unstructured"
name="Wide Landscape"
ratio="0.6180"/>
<landscape
jcr:primaryType="nt:unstructured"
name="Landscape"
ratio="0.8284"/>
<square
jcr:primaryType="nt:unstructured"
name="Square"
ratio="1"/>
<portrait
jcr:primaryType="nt:unstructured"
name="Portrait"
ratio="1.6180"/>
</aspectRatios>
</crop>
<flip
jcr:primaryType="nt:unstructured"
supportedMimeTypes="[image/jpeg,image/png,image/webp,image/tiff]"
features="-"/>
<map
jcr:primaryType="nt:unstructured"
supportedMimeTypes="[image/jpeg,image/png,image/webp,image/tiff,image/svg+xml]"
features="*"/>
<rotate
jcr:primaryType="nt:unstructured"
supportedMimeTypes="[image/jpeg,image/png,image/webp,image/tiff]"
features="*"/>
<zoom
jcr:primaryType="nt:unstructured"
supportedMimeTypes="[image/jpeg,image/png,image/webp,image/tiff]"
features="*"/>
</plugins>
<ui jcr:primaryType="nt:unstructured">
<inline
jcr:primaryType="nt:unstructured"
toolbar="[crop#launch,rotate#right,history#undo,history#redo,fullscreen#fullscreen,control#close,control#finish]">
<replacementToolbars
jcr:primaryType="nt:unstructured"
crop="[crop#identifier,crop#unlaunch,crop#confirm]"/>
</inline>
<fullscreen jcr:primaryType="nt:unstructured">
<toolbar
jcr:primaryType="nt:unstructured"
left="[crop#launchwithratio,rotate#right,flip#horizontal,flip#vertical,zoom#reset100,zoom#popupslider]"
right="[history#undo,history#redo,fullscreen#fullscreenexit]"/>
<replacementToolbars jcr:primaryType="nt:unstructured">
<crop
jcr:primaryType="nt:unstructured"
left="[crop#identifier]"
right="[crop#unlaunch,crop#confirm]"/>
<map
jcr:primaryType="nt:unstructured"
left="[map#rectangle,map#circle,map#polygon]"
right="[map#unlaunch,map#confirm]"/>
</replacementToolbars>
</fullscreen>
</ui>
</inplaceEditingConfig>
</cq:inplaceEditing>
</jcr:root>
3. The unique aspect of the file is the <parameters> node that sets the resourceType to wknd-spa-vue/components/banner.
<parameters
jcr:primaryType="nt:unstructured"
sling:resourceType="wknd-spa-vue/components/banner"
imageCrop=""
imageMap=""
imageRotate=""/>
Most component’s do not require a _cq_editConfig. Image components and descendants are the exception.
Extend the Dialog
Our Banner component requires an extra text field in the dialog to capture the bannerText. Since we are using Sling inheritance, we can use features of the Sling Resource Merger to override or extend portions of the dialog. In this sample a new tab has been added to the dialog to capture additional data from an author to populate the Card Component.
- In the
ui.appsmodule, beneath thebannerfolder, create a folder named_cq_dialog. - Beneath
_cq_dialogcreate a Dialog definition file.content.xml. Populate it with the following:
<?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="Banner"
sling:resourceType="cq/gui/components/authoring/dialog">
<content jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<tabs jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<text
jcr:primaryType="nt:unstructured"
jcr:title="Text"
sling:orderBefore="asset"
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">
<textGroup
granite:hide="${cqDesign.titleHidden}"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/well">
<items jcr:primaryType="nt:unstructured">
<bannerText
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldDescription="Text to display on top of the banner."
fieldLabel="Banner Text"
name="./bannerText"/>
</items>
</textGroup>
</items>
</column>
</items>
</columns>
</items>
</text>
</items>
</tabs>
</items>
</content>
</jcr:root>
The above XML definition will create a new tab named Text and order it before the existing Asset tab. It will contain a single field Banner Text.
The dialog will look like the following:

Observe that we did not have to define the tabs for Asset or Metadata. These are inherited via the sling:resourceSuperType property.
Before we can preview the dialog, we need to implement the SPA Component and the MapTo function.
Implement SPA Component
In order to use the Banner component with the SPA Editor, a new SPA component must be created that will map to wknd-spa-vue/components/banner. This will be done in the ui.frontend module.
- In the
ui.frontendmodule create a new folder forBanneratui.frontend/src/components/Banner. - Create a new file named
Banner.jsbeneath theBannerfolder. Populate it with the following:
<template>
<div class="Banner">
<h4>{{bannerText}}</h4>
<div class="BannerImage">
<img
class="Image-src"
:src="src"
:alt="alt"
:title="title ? title : alt" />
</div>
</div>
</template>
<script>
export default {
name: 'Banner',
props: {
src: {
type: String
},
alt: {
type: String
},
title: {
type: String
},
bannerText: {
type: String
}
}
}
</script>
<style scoped>
</style>
Update map-components.js to include the Banner component:
MapTo('wknd-spa-vue/components/banner')(Banner, {
emptyLabel: 'Banner',
isEmpty: function (props) {
return !props || !props.src || props.src.trim().length < 1
}
})
- At this point the project can be deployed to AEM and the dialog can be tested:
$ cd aem-guides-wknd-spa-vue$ mvn clean install -PautoInstallSinglePackage - Update the SPA Template’s policy to add the
Bannercomponent as an allowed component. - Navigate to a SPA page and add the
Bannercomponent to one of the SPA pages
Add Java Interface
To ultimately expose the values from the component dialog to the Vue component we need to update the Sling Model that populates the JSON for the Banner component. This will be done in the core module that contains all of the Java code for our SPA project.
First we will create a new Java interface for Banner that extends the Image Java interface.
- In the
coremodule create a new file namedBannerModel.javaatcore/src/main/java/com/adobe/aem/guides/wkndspa/vue/core/models. - Populate
BannerModel.javawith the following:
package com.adobe.aem.guides.wknd.spa.vue.core.models;
import com.adobe.cq.wcm.core.components.models.Image;
import org.osgi.annotation.versioning.ProviderType;
@ProviderType
public interface BannerModel extends Image {
public String getBannerText();
}
This will inherit all of the methods from the Core Component Image interface and add one new method getBannerText().
Implement Sling Model
Next, implement the Sling Model for the BannerModel interface.
- In the
coremodule create a new file namedBannerModelImpl.javaatcore/src/main/java/com/adobe/aem/guides/wknd/spa/vue/core/models/impl. - Populate
BannerModelImpl.javawith the following:
package com.adobe.aem.guides.wknd.spa.vue.core.models.impl;
import com.adobe.aem.guides.wknd.spa.vue.core.models.BannerModel;
import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.wcm.core.components.models.Image;
import org.apache.sling.models.annotations.*;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.annotations.via.ResourceSuperType;
@Model(
adaptables = SlingHttpServletRequest.class,
adapters = { BannerModel.class,ComponentExporter.class},
resourceType = BannerModelImpl.RESOURCE_TYPE,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class BannerModelImpl implements BannerModel {
// points to the the component resource path in ui.apps
static final String RESOURCE_TYPE = "wknd-spa-vue/components/banner";
@Self
private SlingHttpServletRequest request;
// With sling inheritance (sling:resourceSuperType) we can adapt the current resource to the Image class
// this allows us to re-use all of the functionality of the Image class, without having to implement it ourself
// see https://github.com/adobe/aem-core-wcm-components/wiki/Delegation-Pattern-for-Sling-Models
@Self
@Via(type = ResourceSuperType.class)
private Image image;
// map the property saved by the dialog to a variable named `bannerText`
@ValueMapValue
private String bannerText;
// public getter to expose the value of `bannerText` via the Sling Model and JSON output
@Override
public String getBannerText() {
return bannerText;
}
// Re-use the Image class for all other methods:
@Override
public String getSrc() {
return null != image ? image.getSrc() : null;
}
@Override
public String getAlt() {
return null != image ? image.getAlt() : null;
}
@Override
public String getTitle() {
return null != image ? image.getTitle() : null;
}
// method required by `ComponentExporter` interface
// exposes a JSON property named `:type` with a value of `wknd-spa-vue/components/banner`
// required to map the JSON export to the SPA component props via the `MapTo`
@Override
public String getExportedType() {
return BannerModelImpl.RESOURCE_TYPE;
}
}
Notice the use of the @Model and @Exporter annotations to ensure the Sling Model is able to be serialized as JSON via the Sling Model Exporter.
BannerModelImpl.java uses the Delegation pattern for Sling Models to avoid rewriting all of the logic from the Image core component.
Observe the following lines:
@Self
@Via(type = ResourceSuperType.class)
private Image image;
The above annotation will instantiate an Image object named image based on the sling:resourceSuperType inheritance of the Banner component.
@Override
public String getSrc() {
return null != image ? image.getSrc() : null;
}
It is then possible to simply use the image object to implement methods defined by the Image interface, without having to write the logic ourselves. This technique is used for getSrc(), getAlt() and getTitle().
Open a terminal window and deploy just the updates to the core module using the Maven autoInstallBundle profile from the core directory.
Putting it all together
- Return to AEM and open the SPA page that has the
Bannercomponent. - Update the
Bannercomponent to include Banner Text:

Populate the component with an image:

Save the dialog updates.
You should now see the rendered value of Banner Text
Congratulations!
Congratulations, you learned how to extend an AEM component using the and how Sling Models and dialogs work with the JSON model.