Blogs

Blogs

Override Taglib Mini Shopping Cart (with React) in Liferay Commerce

NOTICE: This blog  post has Spanish and English version.

When you start to build an e-commerce website with Liferay Commerce, you might be faced with a requirement from a customer to personalize the “Mini Shopping Cart”. 

You can use a Widget Display Template to create your own entire custom style and apply it to the “Mini Cart” module. But suppose you want to make just a minor change , because you like the mini-car commerce taglib styles and you don’t want to have to create an entire structure and style with a freemarker.

So... you start to look at how to override some part of the existing cart in Liferay Commerce (of course because we don’t want to reinvent the wheel, we only want to maybe add some small piece, or hide some existing part in this cart). But to your surprise, there is not a “standardized Liferay way” to override it.

It is not a JSP which we are comfortable with and know how to handle. It is a Taglib built with React files… 

Don't panic, here I have the step-by-step recipe on how to do it. 

First of all… 

Go to your Liferay Portal > Control Panel > Sites > click to add a new site and select a SpeedWell Template, to create a new e-commerce Site for our test with the mini cart. 


 

  • Go to the Catalog page and add some products to the cart. At the top right hand corner you can see the mini cart with the new product added. Here is the piece with which you’ll learn how to customize in this blog post.

So, let's get to know the code a little bit. It’s useful to know and understand the taglibs from Liferay Commerce, here is a link to help you: https://github.com/liferay/liferay-portal/blob/7.4.1-ga2/modules/apps/commerce/commerce-frontend-taglib/src/main/resources/META-INF/resources

Good to know: when you see the mini-cart taglib code, you will notice that it is called a react component in your Liferay Module, simply using the <aui:script require="${yourComponen}”> 
 

So, the taglibs we saw were built with React Components, and here we have the Components used: https://github.com/liferay/liferay-portal/tree/7.4.1-ga2/modules/apps/commerce/commerce-frontend-js/src/main/resources/META-INF/resources/components

 

So lets start to create our taglib overriding the mini shopping cart OOTB. 

1. I always like to use the blade and starting with a liferay-workspace:

     blade init -v portal-7.4-ga2 liferay-workspace-7-4

      

2. For this test create a API module inside your liferay-workspace, with the following command:

     blade create -t api commerce-frontend-taglib-custom

 

3. Inside your module, delete the package created by default and create these two java classes for our taglib:

     a. com.liferay.commerce.frontend.taglib.custom.internal.servlet.ServletContextUtil

             * Inside copy and paste the following code

package com.liferay.commerce.frontend.taglib.custom.internal.servlet;
import org.osgi.service.component.annotations.Reference;
import javax.servlet.ServletContext;
import org.osgi.service.component.annotations.Component;

/**
* @author Roselaine Marques
*/
@Component(immediate = true, service = {})
public class ServletContextUtil {

  private static ServletContext _servletContext;
  @Reference(
          target = "(osgi.web.symbolicname=commerce.frontend.taglib.custom)", unbind = "-"
  )
  protected void setServletContext(ServletContext servletContext) {
      _servletContext = servletContext;
  }

  public static ServletContext getServletContext() {
      return _servletContext;
  }
}

 

b. com.liferay.commerce.frontend.taglib.custom.CommerceFrontendTaglibCustom

       *Inside copy and paste the following code

package com.liferay.commerce.frontend.taglib.custom;
import com.liferay.taglib.util.IncludeTag;
import com.liferay.commerce.frontend.taglib.custom.internal.servlet.ServletContextUtil;
import javax.servlet.jsp.PageContext;

/**
* @author Roselaine Marques
*/
public class CommerceFrontendTaglibCustom extends IncludeTag {
  @Override
  public void setPageContext(PageContext pageContext) {
      super.setPageContext(pageContext);

      setServletContext(ServletContextUtil.getServletContext());
  }

  @Override
  protected String getPage() {
      return _PAGE;
  }

  private static final String _PAGE = "/mini-cart/page.jsp";
}

 

4. Open the bnd.bnd file and add the following configuration

Bundle-Name: Commerce Frontend Taglib Custom
Bundle-SymbolicName: commerce.frontend.taglib.custom
Bundle-Version: 1.0.0
Export-Package: com.liferay.commerce.frontend.taglib.custom
Provide-Capability:\
  osgi.extender;\
    osgi.extender="jsp.taglib";\
    uri="http://liferay.com/tld/commerce-ui-custom";\
    version:Version="${Bundle-Version}"
Web-ContextPath: /commerce-frontend-taglib-custom

 

NOTE: The main target here is to know how to customize a React taglib from commerce OOTB. These two previous steps are only about how to create a simple and common taglib, I don’t want to bore you by going into too much detail.


5) Now in “commerce-frontend-taglib-custom/src/main/resources”, eliminate everything inside and create the following structure of directories and files:   

            └── META-INF

                ├── liferay-commerce-ui-custom.tld

                ├── resources

                │   ├── init.jsp

                │   ├── js

                │   │   └── OverriddenCartItem.js

                │   └── mini-cart

                │       └── page.jsp

                └── taglib-mappings.properties

      a. In the liferay-commerce-ui-custom.tld add the following code:

<?xml version="1.0"?>
<taglib
      version="2.1"
      xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
>
  <description>Provides the Liferay Commerce MiniCart component tags, prefixed with <![CDATA[<code>commerce-ui-custom:</code>]]>. </description>
  <tlib-version>1.0</tlib-version>
  <short-name>liferay-commerce-ui-custom</short-name>
  <uri>http://liferay.com/tld/commerce-ui-custom</uri>
  <tag>
      <name>mini-cart</name>
      <tag-class>com.liferay.commerce.frontend.taglib.custom.CommerceFrontendTaglibCustom</tag-class>
      <body-content>JSP</body-content>
  </tag>
</taglib>

 

     b. In the init.jsp add the following code:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>
<%@ taglib uri="http://liferay.com/tld/frontend" prefix="liferay-frontend" %>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>

<liferay-frontend:defineObjects />
<liferay-theme:defineObjects />
<portlet:defineObjects />

 

    c. In the taglib-mappings.properties file add the following code:

liferay_commerce_ui_custom=/META-INF/liferay-commerce-ui-custom.tld

 

NOTE: Now we arrive at the main point of this blog post. Let's see how to “override” a React Commerce Taglib in Liferay. We want to customize the mini-cart, so in commerce-frontend-js/src/main/resources/META-INF/resources/components/mini_cart/util/views.js file is where the View files to replace from the original component are: 
 

 

6. As an example, let's replace the original CartItem component with our custom OverriddenCartItem.js. Go to the CartItem.js copy all the code inside and paste it in your OverriddenCartItem.js file and save it.

     a. Now, change the relative imports path to absolute, as in the code below:

import {PRODUCT_REMOVED_FROM_CART} from 'commerce-frontend-js/utilities/eventsDefinitions';
import Price from 'commerce-frontend-js/components/price/Price';
import QuantitySelector from 'commerce-frontend-js/components/quantity_selector/QuantitySelector';
import ItemInfoView from 'commerce-frontend-js/components/mini_cart/CartItemViews/ItemInfoView';
import MiniCartContext from 'commerce-frontend-js/components/mini_cart/MiniCartContext';
import {
  INITIAL_ITEM_STATE,
  REMOVAL_CANCELING_TIMEOUT,
  REMOVAL_ERRORS_TIMEOUT,
  REMOVAL_TIMEOUT,
} from 'commerce-frontend-js/components/mini_cart/util/constants';
import {parseOptions} from 'commerce-frontend-js/components/mini_cart/util/index';

 

      b. Search for this block code and change it for a hardcode to test it if is getting our JS override file, as below:

<ItemInfoView
  childItems={childItems}
  name={name}
  options={options}
  sku="[SKU hardcoded]"
/>

 

    c. Finally, search in the code for the name “CartItem” and replace everything to “OverriddenCartItem”.

PS.: Here I wrote “override” in quotation marks, because in fact you are not overriding the file, you are sending another file for one target “position”. It will be completely clear at the end of this blog tutorial. 

As mentioned before, we will “override one file” from the original commerce taglib. Keep in mind that you will declare this taglib in the page.jsp passing the OverriddenCartItem.js file to replace the CartItem.js component.

7. Open your page.jsp file from your module and add the following code:

<%@ include file="../init.jsp" %>
<%@ page import="com.liferay.portal.kernel.util.HashMapBuilder" %>
<%@ taglib prefix="liferay-commerce-ui" uri="http://liferay.com/tld/commerce-ui" %>

<liferay-commerce-ui:mini-cart views="<%=HashMapBuilder.<String, String>put("Item", "commerce-frontend-taglib-custom@1.0.0/js/OverriddenCartItem").build()%>" />

 

NOTE: As you can see above, we are declaring a taglib as usual, the only difference here is the parameter “views” delecared. Here is where we have a point of extension between the original taglib file to our view component file.

We add the key “Item” because we want to replace this component for the new one (as I show above in the view.js from the mini_cart the CartItem is associated with the key Item). 

We add the value “commerce-frontend-taglib-custom@1.0.0/js/OverriddenCartItem” which will be our custom file.

PS: This value added is the path of the file after compiled, compose by:  “bundle-name” + “version-of-module” + “folder-with-js-file” + “your-file”

8. In your root project folder, create three more files:

   a) .npmbundlerrc

Here we declare the import dependencies needed for our component, so copy and paste the following code:

{
  "config": {
    "imports": {
        "commerce-frontend-js": {
          "/": ">=4.0.0"
        },
        "frontend-js-web": {
          "/": ">=3.0.0"
        },
        "frontend-taglib-clay": {
          "@clayui/icon": ">=3.1.0",
              "@clayui/button": ">=3.6.0"
        },
        "@liferay/frontend-js-react-web": {
                "/": ">=1.0.0",
                "classnames": ">=2.2.6",
                "formik": ">=1.4.3",
                "prop-types": ">=15.7.2",
                "react": ">=16.8.6",
                "react-dnd": ">=7.0.2",
                "react-dnd-html5-backend": ">=7.0.2",
                "react-dom": ">=16.8.6"
        }
    }
  }
}

    b) Package.json with the following code (the “name” here should be the same as your project module name):

{
"dependencies": {},
"devDependencies": {
  "@babel/cli": "^7.7.5",
  "@babel/core": "^7.7.5",
  "@babel/preset-env": "^7.7.6",
  "@babel/preset-react": "^7.7.4",
  "liferay-npm-bridge-generator": "2.26.0",
  "liferay-npm-bundler": "2.24.3"
},
"name": "commerce-frontend-taglib-custom",
"scripts": {
  "build": "babel --source-maps -d build/resources/main/META-INF/resources src/main/resources/META-INF/resources && liferay-npm-bundler && liferay-npm-bridge-generator --verbose"
},
"version": "1.0.0"
}

 

   c) .babelrc with the following code:

{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}

 

8. Now in your theme you can use your custom TagLib using a macro liferay_commerce_ui_custom. See example below:

<div class="speedwell-topbar__cart-wrapper speedwell-cart">
  <@liferay_commerce_ui_custom["mini-cart"] />
</div>
 

As the result, you should see your mini-cart overridden, as in the image below:


 

PS: To test the TagLib, I made a copy of the SpeedWell theme from Liferay and assigned it  to the site created with commerce. You can find this example code explained here in my repo.
 
There is a known issue when you redeploy your taglib and it’s used in a Freemarker with the Liferay CE 7.4 GA2, can be solved by restarting the server. For this reason If you are doing a lot of changes and want to avoid restarting the server, use the taglib in some portlet as I’ve created here, and after you get ready your taglib you can setup in your Theme or Widget Display Template.