Étendre un composant principal

Découvrez comment étendre un composant principal existant à utiliser avec l’éditeur SPA d’AEM. Comprendre comment étendre un composant existant est une technique puissante pour personnaliser et étendre les fonctionnalités d’une implémentation d’AEM SPA éditeur.

Objectif

  1. Étendez un composant principal existant avec des propriétés et du contenu supplémentaires.
  2. Comprenez les principes de base de l’héritage des composants à l’aide de sling:resourceSuperType.
  3. Découvrez comment tirer parti de Modèle de délégation pour les modèles Sling afin de réutiliser la logique et les fonctionnalités existantes.

Ce que vous allez créer

Ce chapitre illustre le code supplémentaire nécessaire pour ajouter une propriété supplémentaire à un composant Image standard afin de répondre aux exigences d’un nouveau composant Banner. Le composant Banner contient toutes les mêmes propriétés que le composant Image standard, mais comprend une propriété supplémentaire pour que les utilisateurs puissent remplir le texte de bannière.

Composant de bannière créé final

Prérequis

Examinez les outils et instructions requis pour configurer un environnement de développement local. À ce stade du tutoriel, les utilisateurs disposent d’une bonne compréhension de la fonction AEM Éditeur SPA.

Héritage avec Sling Resource Super Type

Pour étendre un composant existant, définissez une propriété nommée sling:resourceSuperType sur la définition de votre composant. sling:resourceSuperTypeest une 🔗 propriété qui peut être définie sur la définition d’un composant AEM qui pointe vers un autre composant. Cela définit explicitement le composant pour hériter de toutes les fonctionnalités du composant identifié comme le sling:resourceSuperType.

Si nous souhaitons étendre le composant Image à wknd-spa-react/components/image, nous devons mettre à jour le code dans le module ui.apps.

  1. Créez un dossier sous le module ui.apps pour banner à ui.apps/src/main/content/jcr_root/apps/wknd-spa-react/components/banner.

  2. Sous banner, créez une définition de composant (.content.xml) comme suit :

    <?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 est ainsi défini pour hériter de toutes les fonctionnalités de wknd-spa-react/components/image.

cq:editConfig

Le fichier _cq_editConfig.xml détermine le comportement de glisser-déposer dans l’interface utilisateur de création d’AEM. Lors de l’extension du composant Image, il est important que le type de ressource corresponde au composant lui-même.

  1. Dans le module ui.apps , créez un autre fichier sous banner nommé _cq_editConfig.xml.

  2. Renseignez _cq_editConfig.xml avec le code XML suivant :

    <?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. L’aspect unique du fichier est le noeud <parameters> qui définit resourceType sur wknd-spa-react/components/banner.

    <parameters
        jcr:primaryType="nt:unstructured"
        sling:resourceType="wknd-spa-react/components/banner"
        imageCrop=""
        imageMap=""
        imageRotate=""/>
    

    La plupart des composants ne nécessitent pas de _cq_editConfig. Les composants d’image et les descendants sont l’exception.

Étendre la boîte de dialogue

Notre composant Banner nécessite un champ de texte supplémentaire dans la boîte de dialogue pour capturer la balise bannerText. Puisque nous utilisons l’héritage Sling, nous pouvons utiliser les fonctionnalités de Sling Resource Merger pour remplacer ou étendre des parties de la boîte de dialogue. Dans cet exemple, un nouvel onglet a été ajouté à la boîte de dialogue pour capturer des données supplémentaires d’un auteur afin de renseigner le composant Carte.

  1. Dans le module ui.apps, sous le dossier banner, créez un dossier nommé _cq_dialog.

  2. Sous _cq_dialog, créez un fichier de définition de boîte de dialogue .content.xml. Renseignez-le avec les éléments suivants :

    <?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>
    

    La définition XML ci-dessus crée un nouvel onglet nommé Texte et le classe avant l’onglet Ressource existant. Il contiendra un seul champ Texte de bannière.

  3. La boîte de dialogue se présente comme suit :

    Boîte de dialogue de fin de bannière

    Notez que nous n’avions pas à définir les onglets pour Ressource ou Métadonnées. Ils sont hérités via la propriété sling:resourceSuperType .

    Avant de pouvoir prévisualiser la boîte de dialogue, nous devons implémenter le composant SPA et la fonction MapTo.

Mise en oeuvre SPA composant

Pour utiliser le composant Bannière avec l’éditeur de SPA, un nouveau composant SPA doit être créé pour correspondre à wknd-spa-react/components/banner. Cela sera effectué dans le module ui.frontend.

  1. Dans le module ui.frontend , créez un dossier pour Banner à ui.frontend/src/components/Banner.

  2. Créez un fichier nommé Banner.js sous le dossier Banner. Renseignez-le avec les éléments suivants :

    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);
    

    Ce composant SPA est mappé sur le composant AEM wknd-spa-react/components/banner créé précédemment.

  3. Mettez à jour import-components.js à ui.frontend/src/components/import-components.js pour inclure le nouveau composant Banner SPA :

      import './ExperienceFragment/ExperienceFragment';
      import './OpenWeather/OpenWeather';
    + import './Banner/Banner';
    
  4. À ce stade, le projet peut être déployé sur AEM et la boîte de dialogue peut être testée. Déployez le projet à l’aide de vos compétences Maven :

    $ cd aem-guides-wknd-spa.react
    $ mvn clean install -PautoInstallSinglePackage
    
  5. Mettez à jour la stratégie du modèle SPA pour ajouter le composant Banner en tant que composant autorisé.

  6. Accédez à une page SPA et ajoutez le composant Banner à l’une des pages SPA :

    Ajouter un composant Bannière

    REMARQUE

    La boîte de dialogue vous permet d’enregistrer une valeur pour Texte de bannière mais cette valeur n’est pas reflétée dans le composant SPA. Pour l’activer, nous devons étendre le modèle Sling pour le composant.

Ajout d’une interface Java

Pour exposer finalement les valeurs de la boîte de dialogue du composant au composant React, nous devons mettre à jour le modèle Sling qui renseigne le fichier JSON pour le composant Banner. Cela sera effectué dans le module core qui contient tout le code Java pour notre projet SPA.

Nous allons tout d’abord créer une interface Java pour Banner qui étend l’interface Java Image.

  1. Dans le module core , créez un fichier nommé BannerModel.java à core/src/main/java/com/adobe/aem/guides/wkndspa/react/core/models.

  2. Remplissez BannerModel.java avec les éléments suivants :

    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();
    
    }
    

    Cela héritera de toutes les méthodes de l’interface Image du composant principal et ajoutera une nouvelle méthode getBannerText().

Implémentation du modèle Sling

Implémentez ensuite le modèle Sling pour l’interface BannerModel.

  1. Dans le module core , créez un fichier nommé BannerModelImpl.java à core/src/main/java/com/adobe/aem/guides/wkndspa/react/core/models/impl.

  2. Remplissez BannerModelImpl.java avec les éléments suivants :

    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;
        }
    
    }
    

    Notez l’utilisation des annotations @Model et @Exporter pour vous assurer que le modèle Sling peut être sérialisé en tant que JSON via l’exportateur de modèle Sling.

    BannerModelImpl.java utilise le modèle de délégation pour les modèles Sling afin d’éviter de réécrire toute la logique du composant principal Image .

  3. Observez les lignes suivantes :

    @Self
    @Via(type = ResourceSuperType.class)
    private Image image;
    

    L’annotation ci-dessus instancie un objet Image nommé image en fonction de l’héritage sling:resourceSuperType du composant Banner.

    @Override
    public String getSrc() {
        return null != image ? image.getSrc() : null;
    }
    

    Il est alors possible d’utiliser simplement l’objet image pour implémenter des méthodes définies par l’interface Image, sans avoir à écrire la logique nous-mêmes. Cette technique est utilisée pour getSrc(), getAlt() et getTitle().

  4. Ouvrez une fenêtre de terminal et déployez uniquement les mises à jour du module core à l’aide du profil Maven autoInstallBundle du répertoire core.

    $ cd core/
    $ mvn clean install -PautoInstallBundle
    

Assemblage

  1. Revenez à AEM et ouvrez la page SPA qui comporte le composant Banner.

  2. Mettez à jour le composant Banner pour inclure Texte de bannière :

    Texte de bannière

  3. Renseignez le composant avec une image :

    Ajout d’une image à la boîte de dialogue de bannière

    Enregistrez les mises à jour de la boîte de dialogue.

  4. Vous devriez maintenant voir la valeur rendue de Banner Text :

    Texte de bannière affiché

  5. Affichez la réponse du modèle JSON à l’adresse : http://localhost:4502/content/wknd-spa-react/us/en.model.json et recherchez 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"
     },
    

    Notez que le modèle JSON est mis à jour avec des paires clé/valeur supplémentaires après la mise en oeuvre du modèle Sling dans BannerModelImpl.java.

Félicitations !

Félicitations, vous avez appris à étendre un composant AEM à l’aide de et du fonctionnement des modèles et boîtes de dialogue Sling avec le modèle JSON.

Sur cette page