扩展核心组件

了解如何扩展要与AEM SPA编辑器一起使用的现有核心组件。 了解如何扩展现有组件是一项功能强大的技术,可用于自定义和扩展AEM SPA Editor实施的功能。

目标

  1. 扩展现有的核心组件以包含其他属性和内容。
  2. 了解使用sling:resourceSuperType的组件继承的基本内容。
  3. 了解如何利用委派模式 for Sling模型来重复使用现有逻辑和功能。

将构建的内容

本章说明了向标准Image组件添加额外属性以满足新Banner组件要求所需的附加代码。 Banner组件包含与标准Image组件相同的所有属性,但还包含一个附加属性,供用户填充​横幅文本

最终创作的横幅组件

前提条件

查看设置本地开发环境所需的工具和说明。 在本教程中,我们假定用户已对AEM SPA编辑器功能有了扎实的了解。

具有Sling资源超类型的继承

要扩展现有组件,请在组件的定义中设置名为sling:resourceSuperType的属性。 sling:resourceSuperType是一个 🔗 属性,可在AEM组件的定义中设置该属性以指向其他组件。这会显式设置组件以继承标识为sling:resourceSuperType的组件的所有功能。

如果要在wknd-spa-react/components/image扩展Image组件,则需要更新ui.apps模块中的代码。

  1. ui.apps模块下方为bannerui.apps/src/main/content/jcr_root/apps/wknd-spa-react/components/banner创建一个新文件夹。

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

_cq_editConfig.xml文件指示了AEM创作UI中的拖放行为。 扩展图像组件时,资源类型必须与组件本身匹配,这一点很重要。

  1. ui.apps模块中,在banner下创建另一个名为_cq_editConfig.xml的文件。

  2. 使用以下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>
    
  3. 文件的唯一方面是将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资源合并器的功能覆盖或扩展对话框的各个部分。 在此示例中,向对话框中添加了一个新选项卡,用于从作者那里捕获用于填充卡片组件的其他数据。

  1. ui.apps模块的banner文件夹下,创建一个名为_cq_dialog的文件夹。

  2. _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​选项卡之前。​它将包含单个字段​横幅文本

  3. 该对话框将如下所示:

    横幅最终对话框

    请注意,我们不必为​Asset​或​Metadata​定义选项卡。 这些属性通过sling:resourceSuperType属性继承。

    在预览对话框之前,我们需要实施SPA组件和MapTo函数。

实施SPA组件

要将横幅组件与SPA编辑器一起使用,必须创建一个新的SPA组件,该组件将映射到wknd-spa-react/components/banner。 此操作将在ui.frontend模块中完成。

  1. ui.frontend模块中,在ui.frontend/src/components/BannerBanner创建新文件夹。

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

  3. ui.frontend/src/components/import-components.js更新import-components.js以包含新的Banner SPA组件:

      import './ExperienceFragment/ExperienceFragment';
      import './OpenWeather/OpenWeather';
    + import './Banner/Banner';
    
  4. 此时,可以将项目部署到AEM,并对对话框进行测试。 使用您的Maven技能部署项目:

    $ cd aem-guides-wknd-spa.react
    $ mvn clean install -PautoInstallSinglePackage
    
  5. 更新SPA模板的策略,将Banner组件添加为​允许的组件

  6. 导航到SPA页面,并将Banner组件添加到其中一个SPA页面:

    添加横幅组件

    注意

    利用对话框,可保存​横幅文本​的值,但此值未反映在SPA组件中。 要启用此功能,我们需要扩展组件的Sling模型。

添加Java界面

要最终将组件对话框中的值显示给React组件,我们需要更新Sling模型,该模型将填充Banner组件的JSON。 此操作将在core模块中完成,该模块包含我们的SPA项目的所有Java代码。

首先,我们将为Banner创建一个新的Java接口,以扩展Image Java接口。

  1. core模块中,在core/src/main/java/com/adobe/aem/guides/wkndspa/react/core/models处创建一个名为BannerModel.java的新文件。

  2. 使用以下内容填充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()

实施Sling模型

接下来,为BannerModel接口实施Sling模型。

  1. core模块中,在core/src/main/java/com/adobe/aem/guides/wkndspa/react/core/models/impl处创建一个名为BannerModelImpl.java的新文件。

  2. 使用以下内容填充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 模型的委派 模式,以避免重写图像核心组件中的所有逻辑。

  3. 请遵循以下行:

    @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()

  4. 打开终端窗口,并使用core目录中的Maven autoInstallBundle配置文件仅部署对core模块的更新。

    $ cd core/
    $ mvn clean install -PautoInstallBundle
    

融于一起

  1. 返回到AEM并打开包含Banner组件的SPA页面。

  2. 更新Banner组件以包含​横幅文本:

    横幅文本

  3. 使用图像填充组件:

    将图像添加到横幅对话框

    保存对话框更新。

  4. 此时您应会看到​横幅文本​的呈现值:

    显示的横幅文本

  5. 在以下位置查看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模型一起使用。

在此页面上