Extending CIF(Commerce Integration Framework) Core Component
In continuation with our last blog on setting up the Venia store, we will customize AEM CIF core components as per our needs.
Adobe Experience Manager (AEM) is a powerful content management solution that enables businesses to deliver exceptional digital experiences. One of its key strengths lies in its Commerce Integration Framework (CIF) Core Components, which facilitates seamless integration of AEM with various commerce platforms. However, to truly stand out from the competition, businesses must go beyond the out-of-the-box features and embrace the art of customization. This blog will explore the benefits of customizing AEM CIF Core Components and guide you through unlocking their full potential.
So far, we have installed and configured the CIF add-on to our local AEM instance and connected it to the Venia store.
Prerequisite:-
- AEM instance with CIF add-on
- Magento Commerce setup with Venia store
- Clone AEM CIF Venia Project: The Venia project can be cloned from Adobe’s official GitHub repo.
Use case
In this blog, we will be focusing on the Product Carousel component to show an “on-sale” icon based on a custom attribute in Magento products. Currently, the Product Carousel component looks like this, and we will add an on-sale icon next to the carousel heading.
After the changes to the core component, the Product Carousel component will show an on-sale icon if any of the products contain the on-sale attribute; this is how it would look.
Why Customize AEM CIF Core Components?
- Tailored User Experience: Customization allows you to create unique and personalized user experiences that align with your brand identity and customer preferences. Tailoring the components to specific target audiences can significantly improve engagement and conversion rates.
- Enhanced Functionality: The out-of-the-box CIF Core Components provide a solid foundation, but every business has unique requirements. Customization empowers you to add new features, modify existing functionalities, or integrate third-party tools seamlessly.
- Performance Optimization: By stripping away unnecessary code and functionalities, you can optimize the performance of CIF Core Components to deliver faster-loading pages, ensuring a smooth user experience.
- Scalability and Future-Proofing: Customizing CIF Core Components allows you to future-proof your digital ecosystem. As your business evolves, you can quickly adapt the components to match new requirements without starting from scratch.
Steps to Customize AEM CIF Core Components
1. First, we will add a custom attribute to the products in Adobe Commerce. The following are the steps to add a custom attribute “on_sale” to a product.
a. Navigate to Magento Admin Console > Catalog > Products.
b. Search for the product and edit it to add the custom attribute e.g., product code: VA24
c. Check the Graphql response using a Graphql IDE. eg.
query Products($filter: ProductAttributeFilterInput) { products(filter: { sku: { eq: "VA24" } ) { items { name sku on_sale } } }
The response should be like this:-
{ "data": { "products": { "items": [ { "name": "Night Out Collection", "sku": "VA24", "on_sale": 1 } ] } } }
2. Create a Custom Component: To avoid modifying the core code directly, create a custom AEM component that inherits from the CIF Core Component you wish to customize. This approach ensures that your changes are isolated and maintainable.
3. Now, we will extend the current sling model of the core component Product Carousel by following the delegation pattern for Sling Models. First, we will create a ProviderType interface that extends the ConsumerType interface of the core component with the additional functionality we want to add in our case, it would be the onSale method.
@ProviderType public interface CustomProductCarousel extends ProductCarousel { public boolean onSale(); }
@Model(adaptables = SlingHttpServletRequest.class, adapters = CustomProductCarousel.class, resourceType = CustomProductCarouselImpl.RESOURCE_TYPE) public class CustomProductCarouselImpl implements CustomProductCarousel { private static final Logger LOGGER = LoggerFactory.getLogger(CustomProductCarouselImpl.class); private static final String RESOURCE_TYPE = "venia/components/commerce/productcarousel"; private static final String ON_SALE_ATTRIBUTE = "on_sale"; @Self @Via(type = ResourceSuperType.class) private ProductCarousel productCarousel; private AbstractProductsRetriever productsRetriever; @PostConstruct public void initModel() { productsRetriever = productCarousel.getProductsRetriever(); if (productsRetriever != null) { // Pass your custom partial query to the ProductRetriever. This class will // automatically take care of executing your query as soon // as you try to access any product property. productsRetriever.extendProductQueryWith(productInterfaceQuery -> productInterfaceQuery.addCustomSimpleField(ON_SALE_ATTRIBUTE)); } } @Override public boolean onSale() { return productsRetriever.fetchProducts().stream().anyMatch(productInterface -> { try { return productInterface.getAsInteger(ON_SALE_ATTRIBUTE) == 1; } catch (SchemaViolationError e) { LOGGER.error("Error retrieving on sale attribute"); } return false; }); } @Override public List<ProductListItem> getProducts() { return productCarousel.getProducts(); } @Override public List<ProductListItem> getProductIdentifiers() { return productCarousel.getProductIdentifiers(); } @Override public AbstractProductsRetriever getProductsRetriever() { return productsRetriever; } @Override public boolean isConfigured() { return productCarousel.isConfigured(); } @Override public String getTitleType() { return productCarousel.getTitleType(); } }
In the above code, the delegation pattern in Sling Models enables MyProductTeaserImpl to use the ProductTeaser model by utilizing the sling:resourceSuperType attribute. For functions you wish to keep as-is without modifications, you can mirror the output of the corresponding ProductCarousel method. In the PostConstruct method, we get the AbstractProductsRetriever object in our model and then extend the Products query with the custom attribute “on_sale.” This way, when the query is executed, it will fetch the on_sale attribute of those products with the other data from graphQL. We have also created a method called “onSale,” which will retrieve attribute values from the products and check if any products have enabled it.
4. Next, we will customize the markup and styles of the component to show the on-sale icon. First, we will modify the HTML to show the on-sale icon next to the Product Carousel heading.
As per the current structure, we will modify two HTML files as follows:-
a. apps/venia/components/commerce/productcarousel/productcarousel.html
<sly data-sly-use.carousel="${'com.venia.core.models.commerce.CustomProductCarouselImpl' @product=properties.product}" data-sly-use.templates="core/wcm/components/commons/v1/templates.html" data-sly-use.carouselTpl="carousel.html" /> <sly data-sly-test.hasProducts="${carousel.isConfigured && carousel.products}" data-sly-call="${carouselTpl.carousel @ carousel = carousel, items = carousel.products, componentType='productcarousel'}"></sly> <sly data-sly-call="${templates.placeholder @ isEmpty = !carousel.isConfigured}" /> <sly data-sly-call="${templates.placeholder @ isEmpty = carousel.isConfigured && !hasProducts, emptyTextAppend = 'Configured, but no products to display'}" />
b. apps/venia/components/commerce/productcarousel/carousel.html
<template data-sly-template.carousel="${@ carousel, items, componentType}" data-sly-use.cardTpl="card.html"> <sly data-sly-test.type="${componentType ? componentType : 'carousel'}" /> <div id="${carousel.id}" data-comp-is="${type}" class="${type}__container" data-cmp-data-layer="${carousel.data.json}"> <div class="${type}__titlecontainer"> <h2 data-sly-element="${carousel.titleType}" data-sly-test="${properties.jcr:title}" class="${type}__title">${properties.jcr:title}</h2> <!-- This is the span tag added for the on-sale icon.--> <span data-sly-test="${carousel.onSale}" class="${type}__sale">sale</span> </div> <button data-carousel-action='prev' class="${type}__btn ${type}__btn--prev" type="button" title="${'Show previous' @ i18n}" aria-label="${'Show previous' @ i18n}" style="display: none;"></button> <button data-carousel-action='next' class="${type}__btn ${type}__btn--next" type="button" title="${'Show next' @ i18n}" aria-label="${'Show next' @ i18n}" style="display: none;"></button> <div class="${type == 'carousel' ? '{0}__cardsroot' : '{0}__root' @ format=[type]}"> <div class="${type}__parent"> <div class="${type}__cardscontainer" data-sly-list.item="${items ? items : carousel.items}"> <sly data-sly-call="${cardTpl.card @ item=item, carousel=carousel}"/> </div> </div> </div> </div> </template>
Now, Accordingly, make the changes in the scss file of the component to show the on-sale icon next to the heading of the carousel. The changes can be made to the following file in the ui.frontend module src/main/styles/commerce/_productcarousel.scss
Finally, we can see the on-sale icon in the product carousel component.
Conclusion
Customizing AEM CIF Core Components gives businesses a competitive edge in delivering personalized and exceptional digital experiences. You can optimize performance, enhance functionality, and future-proof your digital ecosystem by tailoring components to match your unique requirements. Remember to follow best practices, test thoroughly, and document your customizations to ensure a smooth development process. Embrace the power of personalization and elevate your digital presence with AEM CIF Core Components customization.
Stay tuned for our blogs on various other topics.
References: