Monday, August 1, 2022

AEM and Magento Integration Using Commerce Integration Framework(CIF) — Local Setup (Part2)

 In my previous post on this series, we have discussed how to enable local Magento open source commerce platform on local.

Tools Required:

  • AEM 6.5(latest service pack — 6.5.12)
  • Java 11
  • Node JS
  • NPM

CIF addon/Venia sample project setup:

As a first step, download the CIF addon compatible with AEM 6.5 version from the Adobe Software Distribution Website and install it through the package manager on both Author and publisher.

git clone https://github.com/adobe/aem-cif-guides-venia
mvn clean install -PautoInstallSinglePackage,classic

Proxy Server Setup:

As discussed earlier, the CIF react components directly connect(from the browser )with the Magento server to enable the eCommerce functionalities, e.g., Cart Management, Checkout, and accounts management(the product detail and category details are server-side integrated from AEM to Magento), to avoid the CORS issues run a proxy server to proxy the client-side requests to Magento server.

npx local-cors-proxy --proxyUrl http://albin.magento.com/ --port 3002 --proxyPartial /

Configurations:

I am enabling the following configurations through the OSGI console for demo but enable through the code config. The default configurations are placed under aem-cif-guides-venia/classic/ui.config/src/main/content/jcr_root/apps/venia/osgiconfig-classic/config for AEM 6.5(classic) deployment.

CIF GraphQL Client Configuration Factory:

Commerce Config:

Commerce Cloud configuration is enabled under /conf/venia(enabled through code)

http://localhost:4502/content/venia/us/en/products/product-page.html/apollo-running-short.html#MSH02-33-Blackhttp://localhost:4502/content/venia/us/en/products/category-page.html/men/tops-men.html
{"errors":[{"message":"Cannot query field \"applied_gift_cards\" on type \"Cart\".","extensions":{"category":"graphql"},"locations":[{"line":1,"column":536}]}]}

Additional Marketing Contents:

You should be able to associate additional product-specific marketing data to the Product Detail pages through Experience/Content Fragments and Assets.

MSM:

You can use AEM’s MSM capability to support multiple country/language stores in AEM. Refer to the following Adobe Document for details on multi-store setup through MSM — Commerce Multi-Store Setup | Adobe Experience Manager

Specialized Product and Category Pages:

By default, we are using generic Product/Category pages to display the different Category and Product details(displayed through a dynamic selector). If required, you can define the specialized Category/Product pages for specific Categories and Products. Refer to the following document for details on enabling specialized Category/Product Pages — Creating Multiple Category and Product Pages | Adobe Experience Manager

SiteMap:

The CIF framework uses Apache Sling Sitemap to generate https://github.com/apache/sling-org-apache-sling-sitemap#readme. Refer to the following document to get more details on the SiteMap/SEO — https://experienceleague.adobe.com/docs/experience-manager-cloud-service/content/overview/seo-and-url-management.html?lang=en



Wednesday, May 18, 2022

Uncaught TypeError: Cannot read properties of undefined (reading 'add') - React Helmet

 I was getting the below error while using the react-helmet to dynamically change the head section - meta tags, title etc, also the pages were not loading.

scheduler.development.js:171 Uncaught TypeError: Cannot read properties of undefined (reading 'add')

    at e.r.init (Dispatcher.js:53:1)

    at e.r.render (Dispatcher.js:67:1)

    at finishClassComponent (react-dom.development.js:17485:1)

    at updateClassComponent (react-dom.development.js:17435:1)

    at beginWork (react-dom.development.js:19073:1)

    at HTMLUnknownElement.callCallback (react-dom.development.js:3945:1)

    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994:1)

    at invokeGuardedCallback (react-dom.development.js:4056:1)

    at beginWork$1 (react-dom.development.js:23964:1)

    at performUnitOfWork (react-dom.development.js:22776:1)

    at workLoopSync (react-dom.development.js:22707:1)

    at renderRootSync (react-dom.development.js:22670:1)

    at performSyncWorkOnRoot (react-dom.development.js:22293:1)

    at react-dom.development.js:11327:1

    at unstable_runWithPriority (scheduler.development.js:468:1)

    at runWithPriority$1 (react-dom.development.js:11276:1)

    at flushSyncCallbackQueueImpl (react-dom.development.js:11322:1)

    at workLoop (scheduler.development.js:417:1)

    at flushWork (scheduler.development.js:390:1)

    at MessagePort.performWorkUntilDeadline (scheduler.development.js:157:1)


To fix the issue

Replaced react-helmet with  react-helmet-async

npm install react-helmet-async or yarn add react-helmet-async

Wrap the App element with HelmetProvider

import { HelmetProvider } from 'react-helmet-async';

ReactDOM.render(

  <React.StrictMode>

  <HelmetProvider>

    <App />

 </HelmetProvider>

  </React.StrictMode>,

  document.getElementById('root')

);

Now the helmet can be used in individual components to change the dynamic header elements

import { Helmet } from 'react-helmet';

import {  Test   } from './components';

const Test = () => {

  return (

<React.Fragment>

<Helmet>

<title>Test</title>

<meta name="description" content="Test Description" />

<meta property="og:title" content="Test"/>

<meta property="og:description" content="Test" />

  </Helmet>

  <Introduction />

  <Details />

  <BackToTop />

</React.Fragment>

  );

};

export default Test;

After this, I saw the behavior, non of the existing meta tags defined in index.html apart from the title, replaced with the new values. To fix that, I added data-rh="true" to the metatags in index.html that require the change.

<meta property="og:title" content="default value" data-rh="true" />

Somehow data-react-helmet=" true"  property was not working.

Happy Coding!!!


Tuesday, May 10, 2022

How to Share Selected i18n Dictionaries in JSON Format — AEM(Adobe Experience Manager)

 The AEM internationalization framework(i18n) uses dictionaries in the repository to store English strings and their translations in other languages. The framework uses English as the default language. Localized strings can be stored in several dictionaries in the repository, e.g., component level. The AEM internationalization framework combines the dictionaries and makes them available in Sling as a single ResourceBundle object. The i18n can be used in slightly, JavaScript, and Java to display the language-specific labels and values.

ResourceBundleExportServlet:

http://localhost:4502/libs/cq/i18n/dict.de_de.json
http://localhost:4502/libs/cq/i18n/dict.de.json
http://localhost:4502/libs/cq/i18n/dict.fr_fr.json

Client library JSON:

Custom servlet with sling:basename:

http://localhost:4502/bin/fetchi18nvalues/search.it.json
http://localhost:4502/bin/fetchi18nvalues/search.bg.json
http://localhost:4502/bin/fetchi18nvalues/search1.it.json
http://localhost:4502/bin/fetchi18nvalues/search1.bg.json



Friday, May 6, 2022

How to limit the number of components in Container Components?

 In this post, let us see the approaches to limit the components in container components; in some cases, we may have the use case to limit the number of child components allowed to be added into the container due to the business rules.

Option1 — Limit through FE logic:

Option2 — Limit through Edit Config Listeners and Back End Servlet:

function(childEditable) {var path = childEditable.path;
var date = new Date();
var servletURL = '/bin/ComponentLimiterSevlet?' + date.getTime();
var requestData = 'componentPath=' + path;
$.ajax({
async: false,
type: 'GET',
url: servletURL,
dataType: 'text',
data: requestData,
success: function(returnData) {
if (returnData == 'false') {
var cmp = childEditable.getParent();
cmp.refresh();
showErrorAlert('Cannot drop more component, if you want to add more, please set the Max component allowed bigger.', 'Error');
}
}
})
function showErrorAlert(message, title) {
var fui = $(window).adaptTo("foundation-ui"),
options = [{
text: "OK",
warning: true
}];
message = message || "Unknown Error";
title = title || "Error";
fui.prompt(title, message, "error", options);
}
}
import java.io.IOException;import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Session;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.servlets.annotations.SlingServletPaths;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.propertytypes.ServiceDescription;
import com.day.cq.wcm.api.policies.ContentPolicy;
import com.day.cq.wcm.api.policies.ContentPolicyManager;
@Component(service = { Servlet.class })
@SlingServletPaths("/bin/ComponentLimiterSevlet")
@ServiceDescription("Component Limitor Servlet")
public class ComponentLimitorServlet extends SlingSafeMethodsServlet {
private static final long serialVersionUID = 1L;
private String responseMsg = "true";
@Override
protected void doGet(final SlingHttpServletRequest req, final SlingHttpServletResponse resp)
throws ServletException, IOException {
try {
String currentComponentPath = req.getParameter("componentPath");
String resourcePath = currentComponentPath.substring(0, currentComponentPath.lastIndexOf("/"));
ResourceResolver resolver = req.getResourceResolver();Node resourceNode = JcrUtils.getNodeIfExists(resourcePath, resolver.adaptTo(Session.class));if (resourceNode != null) {
int maxComponents = 1;
ContentPolicyManager policyManager = resolver.adaptTo(ContentPolicyManager.class);if (policyManager != null) {
ContentPolicy contentPolicy = policyManager.getPolicy(resolver.getResource(resourcePath));
if (contentPolicy != null && contentPolicy.getProperties().containsKey("maxComponents")) {
maxComponents = Integer
.parseInt(contentPolicy.getProperties().get("maxComponents", String.class));
}
}
int toalComponentCount = 0;
NodeIterator iterator = resourceNode.getNodes();
while (iterator.hasNext()) {
iterator.next();
toalComponentCount++;
}
if (toalComponentCount > maxComponents) {
// Gets currently inserted node
Node currentComponentNode = JcrUtils.getNodeIfExists(currentComponentPath,
resolver.adaptTo(Session.class));
currentComponentNode.remove();
currentComponentNode.getSession().save();
responseMsg = "false";
} else {
responseMsg = "true";
}
}
} catch (Exception e) {
responseMsg = "true";
}
resp.getWriter().print(responseMsg);
}
}