Fermer

août 20, 2024

Composants composites dans AEM SPA (React) / Blogs / Perficient

Composants composites dans AEM SPA (React) / Blogs / Perficient


À propos des composants composites

Les composants composites sont combinaisons de multiple composants eà résidée ou accessible au sein d’un seul parent/contenirest composerent. L’objectif principal de développement composants de telle manière c’est faire la vie plus facile pour auteurs par être capable de glisser-déposer uniquement oc’est composerent plutôt que multiples. Il aide également à créer des composants modulaires réutilisables qui peuvent fonctionner indépendamment ou en tant que partie d’autres composants composites. Dans certains cas, le parent/contenirest composerent a son propre dialogue alors que dans d’autres ce n’est pas le cas. Les auteurs peuvent individuellement modifier chacun des composants comme ils le feraient s’ils étaient ajoutés à la page de manière autonomec’est composerent.

Composants composites dans SPA et non-SPA

Développer des composants composites dans un monde non-SPA/HTL est assez simple. Tout ce que nous avons à faire est d’ajouter le chemin des ressources du composant enfant en utilisant ressource-données-sly à l’intérieur du HTL du composant parent. Nous y reviendrons plus en détail ci-dessous.

Pour obtenir la même chose sur l’architecture AEM SPA, cela implique quelques étapes supplémentaires. En effet, seules les données JSON des composants créés sont exposées via l’exportateur de modèles Sling, tandis que le composant React effectue le gros du travail de mappage avec eux et de lecture des données. Si le JSON n’a pas de structure imbriquée (composants enfants répertoriés sous le parent), le composant React ne sait pas que nous essayons de créer un composant composite. Une fois que nous disposons de la structure imbriquée, nous pouvons itérer et initialiser chacune en tant que composant React autonome.

Plus de détails sont mentionnés ci-dessous dans la section de mise en œuvre.

Mettre en œuvreentrer Nsur-SPA

La manière HTL/non-SPA de construire ce composant peut être réalisée par les étapes suivantes.

Si le composant parent/conteneur n’a pas de boîte de dialogue ou de boîte de dialogue de conception, nous devons créer un cq:modèlenoeud de typent:non structuréet sous cq:modèle nœud., mMplus un nœud de typent:non structuréavec sling:type de ressource ayant la valeur du chemin d’accès au composant inclured.

Répétez la même chose pour tous les composants enfants. Dans le HTL du composant parent/conteneur, ajoutez la balise ci-dessous pour inclure simplement le composant au moment du rendu :

‘ @ resourceType=’‘ }”/>

Vous trouverez ci-dessous un exemple de composant titre et image (composite) construit à l’aide d’un titre et d’une image principaux. Le composant parent/conteneur n’a pas de boîte de dialogue, nous devons donc créer un cq:template comme mentionné ci-dessus. Le HTL devra inclure les composants image et titre en utilisant data-sly-resource.

2. Composants de recherche et de mon compte

  • Créez un composant de recherche autonome.
  • Il peut être réutilisé dans l’en-tête ou glissé-déposé n’importe où.
  • Créez un composant Mon compte avec des champs pour configurer la gestion des comptes.

3. Un modèle ExportChildComponents Sling

  • Le modèle d’exportation Child Components Sling implémente le modèle Core Container Sling.
import com.adobe.cq.export.json.ComponentExporter;
import com.drew.lang.annotations.NotNull;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.wcm.core.components.models.Container;
import java.util.Map;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.HashMap;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.injectorspecific.*;
import org.apache.sling.models.factory.ModelFactory;
import com.adobe.cq.export.json.SlingModelFilter;

//Sling Model annotation 
@Model(adaptables = SlingHttpServletRequest.class, 
       adapters = { ExportChildComponents.class,ComponentExporter.class },
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
       
@Exporter( // Exporter annotation that serializes the model as JSON
        name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, 
        extensions = ExporterConstants.SLING_MODEL_EXTENSION, 
        selector = ExporterConstants.SLING_MODEL_SELECTOR)

public class ExportChildComponents implements Container{
       
    private Map<String, ? extends ComponentExporter> childrenModels;
    private String[] exportedItemsOrder;

    @ScriptVariable
    private Resource resource;
    
    @Self
    private SlingHttpServletRequest request;

    @OSGiService
    private SlingModelFilter slingModelFilter;

    @OSGiService
    private ModelFactory modelFactory;
    
    @NotNull
    @Override
    public Map<String, ? extends ComponentExporter> getExportedItems() {
        if (childrenModels == null) {
            childrenModels = getChildrenModels(request, ComponentExporter.class);
        }
        Map<String, ComponentExporter> exportChildrenModels = new HashMap<String, ComponentExporter>();
        exportChildrenModels.putAll(childrenModels);
        return exportChildrenModels;
    }

    @NotNull
    @Override
    public String[] getExportedItemsOrder() {
        if (exportedItemsOrder == null) {
            Map<String, ? extends ComponentExporter> models = getExportedItems();
            if (!models.isEmpty()) {
                exportedItemsOrder = models.keySet().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
            } else {
                exportedItemsOrder = ArrayUtils.EMPTY_STRING_ARRAY;
            }
        }
        return Arrays.copyOf(exportedItemsOrder,exportedItemsOrder.length);
    }

    private  Map<String, T> getChildrenModels(@NotNull SlingHttpServletRequest request, @NotNull Class
            modelClass) {
        Map<String, T> models = new LinkedHashMap<>();
        for (Resource child : slingModelFilter.filterChildResources(resource.getChildren())) {
            T model = modelFactory.getModelFromWrappedRequest(request, child, modelClass);
            if (model != null) {
                models.put(child.getName(), model);
            }
        }
        return models;
    }
}
...
  • Cela permet de parcourir les composants enfants enregistrés sous le nœud parent.
  • Exportez-les ensuite tous en une seule sortie JSON via leur exportateur de modèle Sling.
import com.adobe.cq.export.json.ComponentExporter;
{
    ":items": {
      "root": {
        "columnClassNames": {
          "header": "aem-GridColumn aem-GridColumn--default--12"
        },
        "gridClassNames": "aem-Grid aem-Grid--12 aem-Grid--default--12",
        "columnCount": 12,
        ":items": {
          "header": {
            "id": "header-346949959",
            ":itemsOrder": [
              "logo",
              "navigation",
              "search",
              "account"
            ],
            ":type": "perficient/components/header",
            ":items": {
              "logo": {
                "id": "image-ee58d4cd48",
                "linkType": "",
                "tagLinkType": "",
                "displayPopupTitle": true,
                "decorative": false,
                "srcUriTemplate": "/content/experience-fragments/perficient/us/en/site/header/master/_jcr_content/root/header/logo.coreimg{.width}.png/1705295980301/perficient-logo-horizontal.png",
                "lazyEnabled": true,
                "title": "Logo",
                "uuid": "799c7831-264c-42c0-be02-1d0cbb747bd2",
                "areas": [],
                "alt": "NA",
                "src": "/content/experience-fragments/perficient/us/en/site/header/master/_jcr_content/root/header/logo.coreimg.png/1705295980301/perficient-logo-horizontal.png",
                "widths": [],
                ":type": "perficient/components/logo"
              },
              "navigation": {
                "id": "navigation-1727181688",
                "items": [
                  {
                    "id": "navigation-cd54619f8f-item-12976048d7",
                    "path": "/content/react-spa/us/en/cases",
                    "level": 0,
                    "active": false,
                    "current": false,
                    "title": "Link 1",
                    "url": "/content/react-spa/us/en/cases.html",
                    "lastModified": 1705203487624,
                    ":type": "perficient/components/structure/page"
                  },
                  {
                    "id": "navigation-cd54619f8f-item-438eb66728",
                    "path": "/content/react-spa/us/en/my-onboarding-cases",
                    "level": 0,
                    "active": false,
                    "current": false,
                    "title": "Link 2",
                    "url": "/content/react-spa/us/en/my-onboarding-cases.html",
                    "lastModified": 1705203496764,
                    ":type": "perficient/components/structure/page"
                  },
                  {
                    "id": "navigation-cd54619f8f-item-e8d85a3188",
                    "path": "/content/react-spa/us/en/learning-resources",
                    "level": 0,
                    "active": false,
                    "current": false,
                    "title": "Link 3",
                    "url": "/content/react-spa/us/en/learning-resources.html",
                    "lastModified": 1705203510651,
                    ":type": "perficient/components/structure/page"
                  },
                  {
                    "id": "navigation-cd54619f8f-item-d1553683aa",
                    "path": "/content/react-spa/us/en/Reports",
                    "children": [],
                    "level": 0,
                    "active": false,
                    "current": false,
                    "title": "Link 5",
                    "url": "/content/react-spa/us/en/Reports.html",
                    "lastModified": 1705203601382,
                    ":type": "perficient/components/structure/page"
                  }
                ],
                ":type": "perficient/components/navigation"
              },
              "search": {
                "id": "search-1116154524",
                "searchFieldType": "navbar",
                ":type": "perficient/components/search"
              },
              "account": {
                "id": "account-1390371799",
                "accountConfigurationFields": [],
                "logoutLinkType": "",
                "helpText": "Account",
                "logoutText": "Sign Out",
                ":type": "perficient/components/account"
              }
            }
          }
        },
        ":itemsOrder": [
          "header"
        ],
        ":type": "perficient/components/core/container"
      }
    }
  }
...

4. Composant d’en-tête parent/conteneur

  • Créez un composant d’en-tête qui est une extension du composant conteneur principal.

Composant d'en-tête Aem Spa

  • Incluez la boîte de dialogue Rechercher un composant en tant qu’onglet supplémentaire dans la boîte de dialogue du composant d’en-tête.

Boîte de dialogue Rechercher un composant

  • Incluez l’onglet Comptes qui est un onglet personnalisé avec des champs pour la gestion des comptes utilisateur.

Onglet Comptes

  • Créez cq:template ayant des nœuds enfants pointant vers leurs chemins de ressources correspondants.
  • Cela aidera à exporter les données créées à l’aide de l’exportateur de modèles Sling.

Exportateur de modèles de fronde

  • Créer HeaderModelImpl qui s’étend Exporter des composants enfants (créé à l’étape 3). Cela exporte les données JSON non seulement du parent mais aussi de ses enfants (logo, navigation et recherche).
import com.adobe.cq.export.json.ComponentExporter;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.chc.ecchub.core.models.ExportChildComponents;
import com.chc.ecchub.core.models.nextgen.header.HeaderModel;

@Model(adaptables = SlingHttpServletRequest.class,
        adapters = { HeaderModel.class, ComponentExporter.class},
        resourceType = HeaderModelImpl.RESOURCE_TYPE, 
        defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)

@Exporter( // Exporter annotation that serializes the model as JSON
        name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, 
        extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class HeaderModelImpl extends ExportChildComponents implements HeaderModel{
    
    static final String RESOURCE_TYPE = "perficient/components/header";

    @Override
    public String getExportedType() {
        return RESOURCE_TYPE;
    }

}
...
  • Créez un équivalent React pour un en-tête qui étend le conteneur principal et mappe au composant d’en-tête créé ci-dessus.
import { Container, MapTo, withComponentMappingContext } from '@adobe/aem-react-editable-components';
import { useState } from "react";
import { NavigationComp, NavigationConfig } from "./Navigation";
import { LogoComp, LogoConfig } from "../../components/Logo";
import { SearchComp } from "../../components/Search";
import { SecondaryNavigation } from "./SecondaryNavigation";

export const HeaderEditConfig = {
    emptyLabel: 'Header'
};

export default class Header extends Container {
    render() {
        return (
            <>
                <HeaderComp headerData={super.childComponents} headerProps={this.props} hasAuthorization={hasAuthorization}/>
            </>
        )
    }
}

const selectedComponent = (props, selectedCqType) => {
    let selectedComponentProps;
    props?.headerData?.forEach((data) => {
        if (data.props?.cqType === selectedCqType) {
            selectedComponentProps = data;
        }
    });
    return selectedComponentProps;
}


const HeaderComp = (props) => {
    const [searchModalOverlay, setSearchModalOverlay] = useState(false);
    const searchProps = selectedComponent(props, 'perficient/components/search');
    const logoProps = selectedComponent(props, 'perficient/components/logo');
    const navProps = selectedComponent(props, 'perficient/components/navigation');

    return (
        <>
            <header data-analytics-component="Header">
                <SearchComp
                    searchProps={searchProps?.props}
                    onToggleSearch={setSearchModalOverlay}
                    searchModal={searchModalOverlay}
                />
                <div className="header__container">
                    <div className="header__container-wrapper">
                        <div className="company-info">
                            {logoProps}
                        </div>
                        <div className="desktop-only">
                            <nav className="navigation">
                                {navProps}
                            </nav>
                            <nav className="header__secondary-nav">
                                <SecondaryNavigation
                                    notificationRead={notificationRead}
                                    isNotificationRead={isNotificationRead}
                                    snProps={props}
                                    onToggleSearch={setSearchModalOverlay}
                                />
                            </nav>
                        </div>
                    </div>
                </div>
            </header>
        </>
    )
}

MapTo('ECCHub/components/nextgen/header/navigation')(NavigationComp, NavigationConfig);
MapTo('ECCHub/components/nextgen/header/logo')(LogoComp, LogoConfig);
MapTo('ECCHub/components/nextgen/header/header')(withComponentMappingContext(Header), HeaderEditConfig);

  • De plus, ajoutez une carte pour le logo et la navigation, car ils sont exportés en tant que composants enfants. Étant donné que l’en-tête étend le conteneur principal, les propriétés du composant enfant sont disponibles via super.childcomponents.
import { useEffect } from 'react';
import { MapTo } from '@adobe/aem-react-editable-components';
import * as constants from '../../../../utils/constants';
import { toggleHeaderForUsers } from '../../../../utils/genericUtil';
import { useDispatch } from "react-redux";
import { BreadcrumbActions } from '../../../../store/Breadcrumb/BreadcrumbSlice';

export const NavigationConfig = {
    emptyLabel: 'Navigation',

    isEmpty: function() {
        return true;
    }
};

export const NavigationComp = (navProps) => {
    return (
            <ul className="top-menu" data-analytics-component="Top Navigation">
                {/* Maps through authored navigation links and renders the link title and URL. */}
                {navProps?.items.map((item, index) =>
                <li key={index}><a href={item?.url} title={item?.title}>{item?.title}</a></li>
                )}
            </ul>
    );
}

MapTo("perficient/components/navigation")(NavigationComp, NavigationConfig);

Super.childcomponents

Vaut l’investissement

Les composants composites sont une fonctionnalité utile à exploiter car ils aident énormément les auteurs tout en conservant la modularité et la réutilisabilité des composants individuels. Bien que la création de l’expérience sur le framework SPA par rapport à l’approche HTL nécessite des efforts supplémentaires, l’investissement en vaut toujours la peine car il s’agit d’une configuration unique et peut être utilisée pour n’importe quel composant un certain nombre de fois car elle est assez générique.






Source link