コアコンポーネントの拡張

既存のコアコンポーネントを拡張してAEM SPA Editorで使用する方法を説明します。 既存のコンポーネントの拡張方法を理解することは、AEM SPA Editor実装の機能をカスタマイズおよび拡張する強力な手法です。

目的

  1. 追加のプロパティとコンテンツを使用して、既存のコアコンポーネントを拡張します。
  2. sling:resourceSuperTypeを使用して、コンポーネントの継承の基本を理解します。
  3. Slingモデルの委任パターンを活用して、既存のロジックと機能を再利用する方法を説明します。

作成する内容

この章では、新しいBannerコンポーネントの要件を満たすために、標準のImageコンポーネントに追加のプロパティを追加するために必要な追加コードを示します。 Bannerコンポーネントには、標準のImageコンポーネントと同じプロパティがすべて含まれますが、バナーテキスト​に入力するための追加のプロパティが含まれています。

最終作成バナーコンポーネント

前提条件

ローカル開発環境の設定に必要なツールと手順を確認します。 この時点で、チュートリアルのユーザーがAEM SPA Editorの機能について明確に理解していることを前提としています。

Slingリソーススーパータイプを使用した継承

既存のコンポーネントを拡張するには、コンポーネントの定義にsling:resourceSuperTypeという名前のプロパティを設定します。 sling:resourceSuperTypeは、別のコ 🔗 ンポーネントを指すAEMコンポーネントの定義に設定できるプロパティです。これにより、sling:resourceSuperTypeと識別されたコンポーネントのすべての機能を継承するようにコンポーネントを明示的に設定します。

Imageコンポーネントをwknd-spa-react/components/imageで拡張する場合は、ui.appsモジュールのコードを更新する必要があります。

  1. ui.appsモジュールの下に、ui.apps/src/main/content/jcr_root/apps/wknd-spa-react/components/bannerbanner用の新しいフォルダーを作成します。

  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/bannerwknd-spa-react/components/imageのすべての機能を継承するよう設定されます。

cq:editConfig

_cq_editConfig.xmlファイルは、AEMオーサリングUIでのドラッグ&ドロップ動作を指示します。 画像コンポーネントを拡張する場合、リソースタイプがコンポーネント自体に一致することが重要です。

  1. ui.appsモジュールで、bannerの下に_cq_editConfig.xmlという名前の別のファイルを作成します。

  2. _cq_editConfig.xmlに次の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 Resource Mergerの機能を使用して、ダイアログの一部を上書きまたは拡張できます。 このサンプルでは、作成者から追加データを取り込み、カードコンポーネントに入力するための新しいタブがダイアログに追加されています。

  1. ui.appsモジュールのbannerフォルダーの下に、_cq_dialogという名前のフォルダーを作成します。

  2. _cq_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​​という名前のタブが並べられます。 1つのフィールド​​バナーテキスト**​が含まれます。

  3. ダイアログは次のようになります。

    バナーの最終ダイアログ

    Asset​または​Metadata​のタブを定義する必要がないことを確認します。 これらはsling:resourceSuperTypeプロパティを介して継承されます。

    ダイアログをプレビューする前に、SPAコンポーネントとMapTo関数を実装する必要があります。

SPAコンポーネントの実装

SPAエディターでバナーコンポーネントを使用するには、wknd-spa-react/components/bannerにマッピングする新しいSPAコンポーネントを作成する必要があります。 これは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.jsimport-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ページに移動し、次のいずれかのSPAページにBannerコンポーネントを追加します。

    バナーコンポーネントの追加

    メモ

    このダイアログでは、Banner Text​の値を保存できますが、この値はSPAコンポーネントには反映されません。 有効にするには、コンポーネントのSling Modelを拡張する必要があります。

Javaインターフェイスの追加

最終的にコンポーネントダイアログの値をReactコンポーネントに公開するには、BannerコンポーネントのJSONを入力するSling Modelを更新する必要があります。 これは、SPAプロジェクトのすべてのJavaコードを含むcoreモジュールで実行されます。

まず、Image Javaインターフェイスを拡張するBanner用の新しいJavaインターフェイスを作成します。

  1. coreモジュールで、core/src/main/java/com/adobe/aem/guides/wkndspa/react/core/modelsBannerModel.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()が1つ追加されます。

Slingモデルの実装

次に、BannerModelインターフェイス用のSling Modelを実装します。

  1. coreモジュールで、core/src/main/java/com/adobe/aem/guides/wkndspa/react/core/models/implBannerModelImpl.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 Model Exporterを介してSling ModelをJSONとしてシリアル化できることに注意してください。

    BannerModelImpl.java は、Sling Modelの委 任パターンを使用 して、画像コアコンポーネントからすべてのロジックが書き換えられないようにします。

  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コンポーネントを更新して、Banner Text​を含めます。

    バナーテキスト

  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"
     },
    

    JSONモデルは、BannerModelImpl.javaにSlingモデルを実装した後、追加のキーと値のペアで更新されます。

おめでとうございます。

これで、を使用してAEMコンポーネントを拡張する方法と、SlingのモデルとダイアログがJSONモデルと連携する方法を学びました。

このページ