了解如何扩展要与AEM SPA编辑器一起使用的现有核心组件。 了解如何扩展现有组件是一项功能强大的技术,可用于自定义和扩展AEM SPA Editor实施的功能。
sling:resourceSuperType
的组件继承的基本内容。本章说明了向标准Image
组件添加额外属性以满足新Banner
组件要求所需的附加代码。 Banner
组件包含与标准Image
组件相同的所有属性,但还包含一个附加属性,供用户填充横幅文本。
查看设置本地开发环境所需的工具和说明。 在本教程中,我们假定用户已对AEM SPA编辑器功能有了扎实的了解。
要扩展现有组件,请在组件的定义中设置名为sling:resourceSuperType
的属性。 sling:resourceSuperType
是一个 🔗 属性,可在AEM组件的定义中设置该属性以指向其他组件。这会显式设置组件以继承标识为sling:resourceSuperType
的组件的所有功能。
如果要在wknd-spa-react/components/image
扩展Image
组件,则需要更新ui.apps
模块中的代码。
在ui.apps
模块下方为banner
的ui.apps/src/main/content/jcr_root/apps/wknd-spa-react/components/banner
创建一个新文件夹。
在banner
下创建组件定义(.content.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"
jcr:primaryType="cq:Component"
jcr:title="Banner"
sling:resourceSuperType="wknd-spa-react/components/image"
componentGroup="WKND SPA React - Content"/>
这会设置wknd-spa-react/components/banner
以继承wknd-spa-react/components/image
的所有功能。
_cq_editConfig.xml
文件指示了AEM创作UI中的拖放行为。 扩展图像组件时,资源类型必须与组件本身匹配,这一点很重要。
在ui.apps
模块中,在banner
下创建另一个名为_cq_editConfig.xml
的文件。
使用以下XML填充_cq_editConfig.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-react/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>
文件的唯一方面是将resourceType设置为wknd-spa-react/components/banner
的<parameters>
节点。
<parameters
jcr:primaryType="nt:unstructured"
sling:resourceType="wknd-spa-react/components/banner"
imageCrop=""
imageMap=""
imageRotate=""/>
大多数组件不需要_cq_editConfig
。 图像组件和子体是例外。
我们的Banner
组件需要对话框中的额外文本字段来捕获bannerText
。 由于我们使用的是Sling继承,因此可以使用Sling资源合并器的功能覆盖或扩展对话框的各个部分。 在此示例中,向对话框中添加了一个新选项卡,用于从作者那里捕获用于填充卡片组件的其他数据。
在ui.apps
模块的banner
文件夹下,创建一个名为_cq_dialog
的文件夹。
在_cq_dialog
下方,创建Dialog定义文件.content.xml
。 使用以下内容填充该变量:
<?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>
上述XML定义将创建一个名为Text的新选项卡,并将其排序在现有Asset选项卡之前。它将包含单个字段横幅文本。
该对话框将如下所示:
请注意,我们不必为Asset或Metadata定义选项卡。 这些属性通过sling:resourceSuperType
属性继承。
在预览对话框之前,我们需要实施SPA组件和MapTo
函数。
要将横幅组件与SPA编辑器一起使用,必须创建一个新的SPA组件,该组件将映射到wknd-spa-react/components/banner
。 此操作将在ui.frontend
模块中完成。
在ui.frontend
模块中,在ui.frontend/src/components/Banner
为Banner
创建新文件夹。
在Banner
文件夹下创建一个名为Banner.js
的新文件。 使用以下内容填充该变量:
import React, {Component} from 'react';
import {MapTo} from '@adobe/aem-react-editable-components';
export const BannerEditConfig = {
emptyLabel: 'Banner',
isEmpty: function(props) {
return !props || !props.src || props.src.trim().length < 1;
}
};
export default class Banner 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} />;
}
// display our custom bannerText property!
get bannerText() {
if(this.props.bannerText) {
return <h4>{this.props.bannerText}</h4>;
}
return null;
}
render() {
if(BannerEditConfig.isEmpty(this.props)) {
return null;
}
return (
<div className="Banner">
{this.bannerText}
<div className="BannerImage">{this.content}</div>
</div>
);
}
}
MapTo('wknd-spa-react/components/banner')(Banner, BannerEditConfig);
此SPA组件映射到之前创建的AEM组件wknd-spa-react/components/banner
。
在ui.frontend/src/components/import-components.js
更新import-components.js
以包含新的Banner
SPA组件:
import './ExperienceFragment/ExperienceFragment';
import './OpenWeather/OpenWeather';
+ import './Banner/Banner';
此时,可以将项目部署到AEM,并对对话框进行测试。 使用您的Maven技能部署项目:
$ cd aem-guides-wknd-spa.react
$ mvn clean install -PautoInstallSinglePackage
更新SPA模板的策略,将Banner
组件添加为允许的组件。
导航到SPA页面,并将Banner
组件添加到其中一个SPA页面:
利用对话框,可保存横幅文本的值,但此值未反映在SPA组件中。 要启用此功能,我们需要扩展组件的Sling模型。
要最终将组件对话框中的值显示给React组件,我们需要更新Sling模型,该模型将填充Banner
组件的JSON。 此操作将在core
模块中完成,该模块包含我们的SPA项目的所有Java代码。
首先,我们将为Banner
创建一个新的Java接口,以扩展Image
Java接口。
在core
模块中,在core/src/main/java/com/adobe/aem/guides/wkndspa/react/core/models
处创建一个名为BannerModel.java
的新文件。
使用以下内容填充BannerModel.java
:
package com.adobe.aem.guides.wkndspa.react.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();
}
这将继承核心组件Image
界面中的所有方法,并添加一个新方法getBannerText()
。
接下来,为BannerModel
接口实施Sling模型。
在core
模块中,在core/src/main/java/com/adobe/aem/guides/wkndspa/react/core/models/impl
处创建一个名为BannerModelImpl.java
的新文件。
使用以下内容填充BannerModelImpl.java
:
package com.adobe.aem.guides.wkndspa.react.core.models.impl;
import com.adobe.aem.guides.wkndspa.react.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-react/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-react/components/banner`
// required to map the JSON export to the SPA component props via the `MapTo`
@Override
public String getExportedType() {
return BannerModelImpl.RESOURCE_TYPE;
}
}
请注意,使用@Model
和@Exporter
注释可确保Sling模型能够通过Sling模型导出器序列化为JSON。
BannerModelImpl.java
使用Sling 模型的委派 模式,以避免重写图像核心组件中的所有逻辑。
请遵循以下行:
@Self
@Via(type = ResourceSuperType.class)
private Image image;
上述注释将根据Banner
组件的sling:resourceSuperType
继承实例化名为image
的图像对象。
@Override
public String getSrc() {
return null != image ? image.getSrc() : null;
}
然后,只需使用image
对象来实现由Image
接口定义的方法,而无需自己编写逻辑。 此技术用于getSrc()
、getAlt()
和getTitle()
。
打开终端窗口,并使用core
目录中的Maven autoInstallBundle
配置文件仅部署对core
模块的更新。
$ cd core/
$ mvn clean install -PautoInstallBundle
返回到AEM并打开包含Banner
组件的SPA页面。
更新Banner
组件以包含横幅文本:
使用图像填充组件:
保存对话框更新。
此时您应会看到横幅文本的呈现值:
在以下位置查看JSON模型响应:http://localhost:4502/content/wknd-spa-react/us/en.model.json并搜索wknd-spa-react/components/card
:
"banner": {
"bannerText": "My Banner Text",
"src": "/content/wknd-spa-react/us/en/home/_jcr_content/root/responsivegrid/banner.coreimg.jpeg/1622167884688/sport-climbing.jpeg",
"alt": "alt banner rock climber",
":type": "wknd-spa-react/components/banner"
},
请注意,在BannerModelImpl.java
中实施Sling模型后,JSON模型会使用其他键/值对进行更新。
恭喜,您学习了如何使用扩展AEM组件,以及Sling模型和对话框如何与JSON模型一起使用。