Como Sobreescribir Mini-Cart Taglib (con React) en Liferay Commerce

IMPORTANTE: esta publicación de blog se realizó con Liferay Portal versión 7.4.1 GA 2, que puede ser usada como guía, pero debe adaptarse a su propia versión (archivo CartItem.js y dependencias de .npmbundlerrc y package.json si es necesario).
NOTICE: This blog  post has Spanish and  English version.

Cuando empieces a crear un sitio web de comercio electrónico con Liferay Commerce, es posible que te enfrentes a un requisito muy común del cliente: la personalización del "Mini Carrito de compra", AKA el mini-cart taglib.

Para empezar a hablar sobre el tema, por si aún no lo conocías, existe un Widget de Mini-Cart y una de las posibilidades para personalizarlo es utilizar una Widget Display Template para crear su propio estilo personalizado completo. 

Pero supón que el requisito de cambio es un cambio pequeño, porque gusta la estructura que tiene la taglib del Commerce y solo se quiere añadir o quitar algún pequeño detalle. En este caso tener que crear una estructura y un estilo completos con Freemarker es mucho trabajo para el poco cambio que hay que hacer en el mini-cart OOTB.

Así que tú, como buen desarrollador que eres, comienzas una búsqueda para ver cómo quitar alguna parte del carrito existente en Liferay Commerce (por supuesto, porque no queremos reinventar la rueda, sólo queremos agregar una pequeña pieza u ocultar alguna pieza existente en este carrito). 

Pero para tu sorpresa, no existe una "manera estándar de Liferay" de la cual estamos acostumbrados, para customizarlo.

No es una JSP con la cual nos sintamos cómodos y sepamos cómo manejar. Es un Taglib construido con archivos React ...

Bien..que no cunda el pánico, aquí tengo la receta paso a paso de cómo hacerlo.

En primer lugar…

Abre en un navegador tu Liferay Portal y haz clic en > Panel de control > Sitios > haz clic para agregar un nuevo sitio y seleccione una plantilla SpeedWell, para crear un nuevo sitio de comercio electrónico para nuestra prueba con el mini carrito.

  • Ve a la página del Catálogo y añade algunos productos al carrito. En la esquina superior derecha puede ver el mini carrito con el nuevo producto agregado. Ahí está la pieza con la que aprenderás a personalizar en esta publicación de blog.

Iniciando nuestra jornada, conozcamos un poco el código. Es muy útil conocer y comprender los taglibs de Liferay Commerce, aquí hay un enlace para ayudarlo:: https://github.com/liferay/liferay-portal/blob/7.4.1-ga2/modules/apps/commerce/commerce-frontend-taglib/src/main/resources/META-INF/resources

Es bueno saber: cuando veas el código de la taglib del mini-cart, notarás que se puede hacer la llamada a un componente de React en tu Módulo Liferay, simplemente usando <aui:script require="${yourComponen}”>
 

Estos taglibs fueron construidos con React Components, y aquí tenemos los Componentes que son y pueden ser utilizados: https://github.com/liferay/liferay-portal/tree/7.4.1-ga2/modules/apps/commerce/commerce-frontend-js/src/main/resources/META-INF/resources/components

 

Así que, comencemos a crear nuestro taglib que sobreescribirá el mini-cart OOTB.

1. Como siempre me gusta utilizar blade y comenzar con liferay-workspace nuevo:

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

      

2. Para este tutorial, con blade, crea un módulo con el template “api” dentro del ${liferay-workspace-}/modules, con el siguiente comando:

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

 

3. Dentro de tu módulo, elimina el paquete creado por defecto y crea estas dos clases Java para la taglib:

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

             * Dentro de tu clase creada, copia y pega el siguiente código:

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

       *Dentro de tu clase creada, copia y pega el siguiente código:

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. Abre el fichero bnd.bnd y añade la siguiente configuración:

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

 

NOTA: El objetivo principal aquí es saber cómo personalizar una taglib OOTB del Commerce hecha con React. Estos dos pasos anteriores son solo sobre cómo crear un taglib simple y común, y como no quiero ser aburrida, en lo que se trata del taglib no entraré en demasiados detalles.


5) Ahora en “commerce-frontend-taglib-custom/src/main/resources”, elimina todo lo que hay dentro y crea la siguiente estructura de directorios y archivos: 

            └── META-INF

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

                ├── resources

                │   ├── init.jsp

                │   ├── js

                │   │   └── OverriddenCartItem.js

                │   └── mini-cart

                │       └── page.jsp

                └── taglib-mappings.properties

      a. En el fichero liferay-commerce-ui-custom.tld agrega el siguiente código:

<?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. En el fichero init.jsp agrega el siguiente código:

<%@ 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. En el fichero taglib-mappings.properties agrega el siguiente código:

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

 

NOTA: Ahora llegamos al punto principal de este tutorial. Veamos cómo "sobreescribir" una Taglib del Liferay Commerce hecha con React. En este caso, queremos personalizar el mini-carrito, por lo que en el archivo commerce-frontend-js/src/main/resources/META-INF/resources/components/mini_cart/util/views.js es donde se encuentran los archivos de View para reemplazar, del componente original:
 

 

6. Como ejemplo, reemplazamos el componente CartItem original con nuestro OverriddenCartItem.js. Ve a CartItem.js, copia todo el código dentro, pégalo en tu archivo OverriddenCartItem.js y guárdalo.

     a. Ahora, cambia la ruta de los import relativa a absoluta, como en el siguiente código:

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. Busca este bloque de código y cámbialo por un hardcode, para probarlo si está obteniendo nuestro componente JS, como se muestra a continuación:

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

 

    c. Finalmente, busca en el código el nombre "CartItem" y reemplaza todo por "OverriddenCartItem".

Ojo: Aquí escribí "sobreescribir" entre comillas, porque de hecho no estamos sobreescribiendo el archivo. En realidad, estamos enviando otro archivo para una "posición" de destino. Quedará completamente claro al final de este tutorial de blog.

Como mencioné anteriormente, "sobreescribiremos un archivo" de la taglib original de Liferay Commerce. Ten en cuenta que tendremos que declarar dicha taglib en un page.jsp pasando el archivo OverriddenCartItem.js para reemplazar el componente CartItem.js.

7. Abre tu archivo page.jsp desde su módulo y agrega el siguiente código:

<%@ 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()%>" />

 

NOTA: Como se puede ver arriba, estamos declarando un taglib como de costumbre, la única diferencia aquí es el parámetro "views" declarado. Aquí es donde tenemos un punto de extensión entre el archivo taglib original y nuestro archivo.

Agregamos la clave “Item” porque queremos reemplazar este componente por el nuevo (como he enseñado arriba en view.js del mini_cart, el componente CartItem está asociado con la clave Item).

Agregamos el valor “commerce-frontend-taglib-custom@1.0.0/js/OverriddenCartItem” que será nuestro archivo personalizado.

Ojo: Este valor agregado es la ruta del archivo después de compilado, compuesto por: “bundle-name” + “version-of-module” + “folder-with-js-file” + “your-file”

8. En la carpeta raíz de su proyecto, crea más tres archivos:

   a) .npmbundlerrc

Aquí declaramos las dependencias de importación necesarias para nuestro componente, así que copia y pega el siguiente código (reemplace por otros nuevos si es necesario de acuerdo con su propio Liferay Portal):

{
  "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 con el siguiente código (el "nombre" aquí debe ser el mismo que el nombre del módulo de su proyecto):

{
"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 con el siguiente código:

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

 

8. Ahora en tu tema podrás utilizar tu TagLib personalizada a partir de la macro liferay_commerce_ui_custom, según el ejemplo a continuación:

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

Como resultado, deberías ver tu mini-cart con tu personalización, como en la imagen a continuación:


 

Ojo: Para probar la TagLib, hice una copia del tema SpeedWell de Liferay y lo asigne al sitio creado con el comercio. Puedes encontrar este código de ejemplo explicado aquí en mi repositorio.
 

Hay una known issue de cuando se vuelve a hacer el deploy la taglib y está siendo utilizada en un Freemarker con Liferay CE 7.4 GA2, que se puede resolver reiniciando el servidor. Por esta razón, si estás haciendo muchos cambios y desea evitar reiniciar el servidor, use el taglib en algún portlet como lo he creado aquí, y una vez que haya preparado su taglib, puede configurarlo en tu tema o WDT.