Map SPA components to AEM components map-components
Learn how to map React components to Adobe Experience Manager (AEM) components with the AEM SPA Editor JS SDK. Component mapping enables users to make dynamic updates to SPA components within the AEM SPA Editor, similar to traditional AEM authoring.
This chapter takes a deeper-dive into the AEM JSON model API and how the JSON content exposed by an AEM component can be automatically injected into a React component as props.
Objective
- Learn how to map AEM components to SPA Components.
- Inspect how a React component uses dynamic properties passed from AEM.
- Learn how to use out of the box React AEM Core Components.
What you will build
This chapter inspects how the provided Text
SPA component is mapped to the AEM Text
component. React Core Components like the Image
SPA component is used in the SPA and authored in AEM. Out of the box features of the Layout Container and Template Editor policies are also be used to create a view that is a little more varied in appearance.
Prerequisites
Review the required tooling and instructions for setting up a local development environment. This chapter is a continuation of the Integrate the SPA chapter, however to follow along all you need is a SPA-enabled AEM project.
Mapping Approach
The basic concept is to map a SPA Component to an AEM Component. AEM components, run server-side, export content as part of the JSON model API. The JSON content is consumed by the SPA, running client-side in the browser. A 1:1 mapping between SPA components and an AEM component is created.
High-level overview of mapping an AEM Component to a React Component
Inspect the Text Component
The AEM Project Archetype provides a Text
component that is mapped to the AEM Text component. This is an example of a content component, in that it renders content from AEM.
Let’s see how the component works.
Inspect the JSON model
-
Before jumping into the SPA code, it is important to understand the JSON model that AEM provides. Navigate to the Core Component Library and view the page for the Text component. The Core Component Library provides examples of all the AEM Core Components.
-
Select the JSON tab for one of the examples:
You should see three properties:
text
,richText
, and:type
.:type
is a reserved property that lists thesling:resourceType
(or path) of the AEM Component. The value of:type
is what is used to map the AEM component to the SPA component.text
andrichText
are additional properties that are exposed to the SPA component. -
View the JSON output at http://localhost:4502/content/wknd-spa-react/us/en.model.json. You should be able to find an entry similar to:
code language-json "text": { "id": "text-a647cec03a", "text": "<p>Hello World! Updated content!</p>\r\n", "richText": true, ":type": "wknd-spa-react/components/text", "dataLayer": {} }
Inspect the Text SPA component
-
In the IDE of your choice open up the AEM Project for the SPA. Expand the
ui.frontend
module and open the fileText.js
underui.frontend/src/components/Text/Text.js
. -
The first area we will inspect is the
class Text
at ~line 40:code language-js class Text extends Component { get richTextContent() { return (<div id={extractModelId(this.props.cqPath)} data-rte-editelement dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(this.props.text)}} /> ); } get textContent() { return <div>{this.props.text}</div>; } render() { return this.props.richText ? this.richTextContent : this.textContent; } }
Text
is a standard React component. The component usesthis.props.richText
to determine whether the content to render is going to be rich text or plain text. The actual “content” used comes fromthis.props.text
.To avoid a potential XSS attack, the rich text is escaped via
DOMPurify
before using dangerouslySetInnerHTML to render the content. Recall therichText
andtext
properties from the JSON model earlier in the exercise. -
Next, open
ui.frontend/src/components/import-components.js
take a look at theTextEditConfig
at ~line 86:code language-js const TextEditConfig = { emptyLabel: 'Text', isEmpty: function(props) { return !props || !props.text || props.text.trim().length < 1; } };
The above code is responsible for determining when to render the placeholder in the AEM author environment. If the
isEmpty
method returns true then the placeholder is rendered. -
Finally take a look at the
MapTo
call at ~line 94:code language-js export default MapTo('wknd-spa-react/components/text')(LazyTextComponent, TextEditConfig);
MapTo
is provided by the AEM SPA Editor JS SDK (@adobe/aem-react-editable-components
). The pathwknd-spa-react/components/text
represents thesling:resourceType
of the AEM component. This path gets matched with the:type
exposed by the JSON model observed earlier.MapTo
takes care of parsing the JSON model response and passing the correct values asprops
to the SPA component.You can find the AEM
Text
component definition atui.apps/src/main/content/jcr_root/apps/wknd-spa-react/components/text
.
Use React Core Components
AEM WCM Components - React Core implementation and AEM WCM Components - Spa editor - React Core implementation. These are a set of re-usable UI components that map to out of the box AEM components. Most projects can re-use these components as a starting point for their own implementation.
-
In the project code open the file
import-components.js
atui.frontend/src/components
.
This file imports all of the SPA components that map to AEM components. Given the dynamic nature of the SPA Editor implementation, we must explicitly reference any SPA components that are tied to AEM author-able components. This allows an AEM author to choose to use a component wherever they want in the application. -
The following import statements include SPA components written in the project:
code language-js import './Page/Page'; import './Text/Text'; import './Container/Container'; import './ExperienceFragment/ExperienceFragment';
-
There are several other
imports
from@adobe/aem-core-components-react-spa
and@adobe/aem-core-components-react-base
. These are importing the React Core components and making them available in the current project. These are then mapped to project specific AEM components using theMapTo
, just like with theText
component example earlier.
Update AEM Policies
Policies are a feature of AEM templates gives developers and power-users granular control over which components are available to be used. The React Core Components are included in the SPA Code but need to be enabled via a policy before they can be used in the application.
-
From the AEM Start screen navigate to Tools > Templates > WKND SPA React.
-
Select and open the SPA Page template for editing.
-
Select the Layout Container and click it’s policy icon to edit the policy:
-
Under Allowed Components > WKND SPA React - Content > check Image, Teaser, and Title.
Under Default Components > Add mapping and choose the Image - WKND SPA React - Content component:
Enter a mime type of
image/*
.Click Done to save the policy updates.
-
In the Layout Container click the policy icon for the Text component.
Create a new policy named WKND SPA Text. Under Plugins > Formatting > check all the boxes to enable additional formatting options:
Under Plugins > Paragraph Styles > check the box to Enable paragraph styles:
Click Done to save the policy update.
Author Content
-
Navigate to the Homepage http://localhost:4502/editor.html/content/wknd-spa-react/us/en/home.html.
-
You should now be able to use the additional components Image, Teaser, and Title on the page.
-
You should also be able to edit the
Text
component and add additional paragraph styles in full-screen mode. -
You should also be able to drag+drop an image from the Asset finder:
-
Experiement with the Title and Teaser components.
-
Add your own images via AEM Assets or install the finished code base for the standard WKND reference site. The WKND reference site includes many images that can be re-used on the WKND SPA. The package can be installed using AEM’s Package Manager.
Inspect the Layout Container
Support for the Layout Container is automatically provided by the AEM SPA Editor SDK. The Layout Container, as indicated by the name, is a container component. Container components are components that accept JSON structures which represent other components and dynamically instantiate them.
Let’s inspect the Layout Container further.
-
In a browser navigate to http://localhost:4502/content/wknd-spa-react/us/en.model.json
The Layout Container component has a
sling:resourceType
ofwcm/foundation/components/responsivegrid
and is recognized by the SPA Editor using the:type
property, just like theText
andImage
components.The same capabilities of re-sizing a component using Layout Mode are available with the SPA Editor.
-
Return to http://localhost:4502/editor.html/content/wknd-spa-react/us/en/home.html. Add additional Image components and try re-sizing them using the Layout option:
-
Re-open the JSON model http://localhost:4502/content/wknd-spa-react/us/en.model.json and observe the
columnClassNames
as part of the JSON:The class name
aem-GridColumn--default--4
indicates the component should be 4 columns wide based on a 12 column grid. More details about the responsive grid can be found here. -
Return to the IDE and in the
ui.apps
module there is a client-side library defined atui.apps/src/main/content/jcr_root/apps/wknd-spa-react/clientlibs/clientlib-grid
. Open the fileless/grid.less
.This file determines the breakpoints (
default
,tablet
, andphone
) used by the Layout Container. This file is intended to be customized per project specifications. Currently the breakpoints are set to1200px
and768px
. -
You should be able to use the responsive capabilities and the updated rich text policies of the
Text
component to author a view like the following:
Congratulations! congratulations
Congratulations, you learned how to map SPA components to AEM Components and you used the React Core Components. You also got a chance to explore the responsive capabilities of the Layout Container.
Next Steps next-steps
Navigation and Routing - Learn how multiple views in the SPA can be supported by mapping to AEM Pages with the SPA Editor SDK. Dynamic navigation is implemented using React Router and React Core Components.
(Bonus) Persist configurations to source control bonus-configs
In many cases, especially at the beginning of an AEM project it is valuable to persist configurations, like templates and related content policies, to source control. This ensures that all developers are working against the same set of content and configurations and can ensure additional consistency between environments. Once a project reaches a certain level of maturity, the practice of managing templates can be turned over to a special group of power users.
The next few steps will take place using the Visual Studio Code IDE and VSCode AEM Sync but could be doing using any tool and any IDE that you have configured to pull or import content from a local instance of AEM.
-
In the Visual Studio Code IDE, ensure that you have VSCode AEM Sync installed via the Marketplace extension:
-
Expand the ui.content module in the Project explorer and navigate to
/conf/wknd-spa-react/settings/wcm/templates
. -
Right+Click the
templates
folder and select Import from AEM Server: -
Repeat the steps to import content but select the policies folder located at
/conf/wknd-spa-react/settings/wcm/templates/policies
. -
Inspect the
filter.xml
file located atui.content/src/main/content/META-INF/vault/filter.xml
.code language-xml <!--ui.content filter.xml--> <?xml version="1.0" encoding="UTF-8"?> <workspaceFilter version="1.0"> <filter root="/conf/wknd-spa-react" mode="merge"/> <filter root="/content/wknd-spa-react" mode="merge"/> <filter root="/content/dam/wknd-spa-react" mode="merge"/> <filter root="/content/experience-fragments/wknd-spa-react" mode="merge"/> </workspaceFilter>
The
filter.xml
file is responsible for identifying the paths of nodes that are installed with the package. Notice themode="merge"
on each of the filters which indicates that existing content will not be modified, only new content is added. Since content authors may be updating these paths, it is important that a code deployment does not overwrite content. See the FileVault documentation for more details on working with filter elements.Compare
ui.content/src/main/content/META-INF/vault/filter.xml
andui.apps/src/main/content/META-INF/vault/filter.xml
to understand the different nodes managed by each module.
(Bonus) Create custom Image Component bonus-image
A SPA Image component has already been provided by the React Core components. However, if you want extra practice, create your own React implementation that maps to the AEM Image component. The Image
component is another example of a content component.
Inspect the JSON
Before jumping into the SPA code, inspect the JSON model provided by AEM.
-
Navigate to the Image examples in the Core Component library.
Properties of
src
,alt
, andtitle
are used to populate the SPAImage
component.note note NOTE There are other Image properties exposed ( lazyEnabled
,widths
) that allow a developer to create an adaptive and lazy-loading component. The component built in this tutorial is simple and does not use these advanced properties.
Implement the Image component
-
Next, create a new folder named
Image
underui.frontend/src/components
. -
Beneath the
Image
folder create a new file namedImage.js
. -
Add the following
import
statements toImage.js
:code language-js import React, {Component} from 'react'; import {MapTo} from '@adobe/aem-react-editable-components';
-
Then add the
ImageEditConfig
to determine when to show the placeholder in AEM:code language-js export const ImageEditConfig = { emptyLabel: 'Image', isEmpty: function(props) { return !props || !props.src || props.src.trim().length < 1; } };
The placeholder will show if the
src
property is not set. -
Next implement the
Image
class:code language-js export default class Image extends Component { get content() { return <img className="Image-src" src={this.props.src} alt={this.props.alt} title={this.props.title ? this.props.title : this.props.alt} />; } render() { if(ImageEditConfig.isEmpty(this.props)) { return null; } return ( <div className="Image"> {this.content} </div> ); } }
The above code will render an
<img>
based on the propssrc
,alt
, andtitle
passed in by the JSON model. -
Add the
MapTo
code to map the React component to the AEM component:code language-js MapTo('wknd-spa-react/components/image')(Image, ImageEditConfig);
Note the string
wknd-spa-react/components/image
corresponds to the location of the AEM component inui.apps
at:ui.apps/src/main/content/jcr_root/apps/wknd-spa-react/components/image
. -
Create a new file named
Image.css
in the same directory and add the following:code language-scss .Image-src { margin: 1rem 0; width: 100%; border: 0; }
-
In
Image.js
add a reference to the file at the top beneath theimport
statements:code language-js import React, {Component} from 'react'; import {MapTo} from '@adobe/aem-react-editable-components'; require('./Image.css');
-
Open the file
ui.frontend/src/components/import-components.js
and add a reference to the newImage
component:code language-js import './Page/Page'; import './Text/Text'; import './Container/Container'; import './ExperienceFragment/ExperienceFragment'; import './Image/Image'; //add reference to Image component
-
In
import-components.js
comment out the React Core Component Image:code language-js //MapTo('wknd-spa-react/components/image')(ImageV2, {isEmpty: ImageV2IsEmptyFn});
This will ensure that our custom Image component is used instead.
-
From the root of the project deploy the SPA code to AEM using Maven:
code language-shell $ cd aem-guides-wknd-spa.react $ mvn clean install -PautoInstallSinglePackage
-
Inspect the SPA in AEM. Any Image components on the page should continue to work. Inspect the rendered output and you should see the markup for our custom Image component instead of the React Core Component.
Custom Image component markup
code language-html <div class="Image"> <img class="Image-src" src="/content/image-src.jpg"> </div>
React Core Component Image markup
code language-html <div class="cmp-image cq-dd-image"> <img src="/content/image-src.jpg" class="cmp-image__image"> </div>
This is a good introduction to extending and implementing your own components.