Build a Communications Widget

Use widgets to create the user interface for your application. You can extend or modify the default CX Commerce and Communications widgets, or create new widgets based on your business scenarios. For example, add a widget to display a list of plans for your shoppers.

For an overview of widgets and the necessary steps to be followed for building a widget, refer to the Create Custom Widgets chapter in the Developing Open Storefront Framework Applications for Oracle CX Commerce guide. All the steps in that chapter are applicable for building a widget for the communications storefront application. However, note the following:

  • The communications storefront application don't implement fetchers or subscribers. If required, you can create a subscriber or fetcher based on your business scenarios.

  • Most of the communications widgets use Buying and Billing API endpoints to retrieve data for the UI. You don't create an endpoint using CX Commerce REST API functions. Here's an example that describes how to build a custom widget for a communications storefront and self-care application.

Let's assume that you're creating custom widgets to display a list of plans on your storefront. You build two widgets, Plans widget and Plan widget, one to display the plans group and the other one to display specific plans.

To build the widgets, you must perform the following steps:

  • Create the directory structure needed for your new widget.

  • Create the initial widget files. These files are later modified as you continue to add other components.

  • Add the widget to your application.

  • Identify and add your widget to your page layout.

  • Create and generate the CSS style sheet for your widget.

  • Display localized strings, if necessary.

Create a Widget Directory Structure

You must build the widgets in the /plugins/components/plans directory. You can create these files and directories manually, or use the create-widget script in the package.json file to create them using templates. Remember that if you copy and modify an existing widget, the structure still remains the same.

Here's the sample widget directory structure for your Plans widget:

/plugins
    /components
        /plans
            index.js
            meta.js
            styles.css
            /locales
                de.js
                en.js

Create Widget Files

Widgets contain an index.js file and a meta.js file. Note that these are different than the index.js and meta.js files that reside in the /plugins directory.

You must create an initial index.js and a meta.js file for your widget and add them to the components directory that you just created.

Here's the sample /plugins/components/plans/index.js file for creating the Plans widget. This file defines that widget and it's available in the @oracle-dx4c-buying/components/plans directory.

import React from 'react';
import Styled from '@oracle-cx-commerce/react-components/styled';
import Plan from '../plan';
import css from './styles.css';
  
 
function Plans(props) {
  //Promotion Offering deliberately hardcoded just for illustrative purposes
  const promotionOffering = [
    {
      id: 'plan1',
      name: 'Supremo Kids',
      description: 'Manage screen time, filter contents and track location on your kids phone and get peace of mind.',
      price: '$45.99',
      marketingFeature: [
        {name: 'Unlimited voice and text, nationwide up to 20 managed contacts'},
        {name: 'Unlimited voice and text, nationwide up to 20 managed contacts'},
        {name: 'Hellicopter parent, enable this feature to watch their every online action'},
        {name: 'Punishment time blocks anything fun'}
      ]
    },
    {
      id: 'plan2',
      name: 'Supremo Zoom',
      description: 'Best for super-fast downloads and UHD streaming, plus everything Unlmited Max can do.',
      price: '$65.99',
      marketingFeature: [
        {name: 'Unlimited voice and text, nationwide up to 20 managed contacts'},
        {name: 'Free calls to Canada and Mexico'}
      ]
    }
  ];
 
  return (
    <Styled id="Plans" css={css}>
      <div>
        <div className="plans-container">
          {promotionOffering &&
            promotionOffering.map(promotion => {
              return (
                <div key={`promotion_${promotion.id}`}>
                  <Plan promotion={promotion} {...props} />
                </div>
              );
            })}
        </div>
      </div>
    </Styled>
  );
}
 
export default Plans;

Here's the sample index.js file for creating the Plan widget, which is a child widget of the Plans widget. This widget also follows the same directory structure as the Plans widget.

import React, {useContext} from 'react';
import {StoreContext} from '@oracle-cx-commerce/react-ui/contexts';
import Styled from '@oracle-cx-commerce/react-components/styled';
import Button from '@oracle-dx4c-buying/components/button';
import css from './styles.css';
 
function Plan(props) {
  const {action} = useContext(StoreContext);
  const {promotion, subTextLabel, choosePlanLabel} = props;
  const {name, description, marketingFeature, price} = promotion;
  const checkMark = '\u2714';
 
  function handleChoosePlan() {
    action('_setPlan', promotion);
  }
 
  return (
    <Styled id="Plan" css={css}>
      <div className="plan-container">
        <div className="plan-title-container">
          <div className="plan-name">{name}</div>
          <div className="plan-price">
            <span className="plan-price-span">
              <span className="plan-price-amount">{price}</span>
              <div className="plan-sub-text">{subTextLabel ? subTextLabel : '\u00A0'}</div>
            </span>
          </div>
        </div>
        <p className="plan-description">{description}</p>
        <div className="plan-info-container">
          {marketingFeature &&
            marketingFeature.map(feature => {
              return (
                <p key={feature} className="plan-feature-info">
                  <span className="plan-check-mark">{checkMark}</span>
                  {feature.name}
                </p>
              );
            })}
        </div>
        <div className="plan-button">
          <Button
            label={choosePlanLabel}
            size="md"
            chroming="solid"
            className="dx4c-brand-button"
            onClick={() => {
              handleChoosePlan();
            }}
          />
        </div>
      </div>
    </Styled>
  );
}
 
export default Plan;

Here's the sample /plugins/components/plans/meta.js file to create a metadata object for the Plans widget:

/**
* Metadata for the plans component.
*/
export default {};

Add Your Widget to Your Application

Now that you have created the index.js and meta.js files for your widget, update both the files to add your widgets to the application. To do so, add the following reference to the /plugins/components/index.js file:

// Export a reference to our _Plans widget.
export const _Plans = () => import('./plans');

Now create a parallel metadata object by adding the following reference in the /plugins/components/meta.js file:

export {default as _Plans} from './plans/meta';
Note:

Preceding the names of your new components with an underscore is the recommended naming convention for custom widgets, as it will prevent naming collisions with default widgets. For example, _Plans.

Identify the Layout

You must create a JSON file (plans.json) under assets < pages to set the layout page for your widget. Here's the sample plans.json file to set the layout page for the Plans widget:

{
    "title": Plan Page",
    "address": "/plan",
    "shortName": "plans",
    "defaultPage": true,
    "layout": [
     {
       "type": "main",
       "width": 12,
       "components": [
         "_Plans"
       ]
     }
    ]
}

Note that the _Plans has been added as a component. To test the custom page locally, use the cx-layout query parameter localhost?cx-layout=plan.

You initially create your layouts locally and then upload them to the Design page of the administration interface. For uploading the layouts using CX Commerce, see the Design Your Store Layout chapter in the Using Oracle CX Commerce guide.

Set the Widget Style Sheet

If you create or use any CSS that's specific to a user interface component, you must store that CSS in the widget styles.css file.

For creating the Plans widget, you use the CSS style sheet defined in the /plugins/components/plans/styles.css file. It contains the standard CSS styling.

Here's the sample CSS for the Plans widget:

.plans-container {
  margin-top: 3rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 27rem));
  grid-column-gap: 1.5rem;
  grid-row-gap: 1.5rem;
  width: auto;
}

Once you create the CSS, update the Plans widget to use the CSS by doing the following:

  1. Import the styles.css file into the component. To do this, enter the following command:

    import css from './styles.css';
  2. Wrap the React element for the widget in a Styled component, with an ID property named after the widget (in this example, Plans) and a CSS property that contains the imported CSS. This adds the CSS to an HTML style element before rendering the widget. For example:

    <Styled id="Plan" css={css}>
         //Widget implementation goes here...
    </Styled>
  3. Use class names from the CSS by setting the React className property on individual React elements. For example:

    <div className="plans-container">
    </div>

Repeat a similar process to add the styles.css Plan component. Here's the sample CSS for the Plan widget:

.plan-container {
          margin-bottom: 2rem;
          text-align: left;
          width: auto;
          }
        .plan-title-container {
          background-color: var(--dx4c-light-yellow-background-color);
          height: 10.8rem;
          padding-top: 2rem;
          padding-right: 2rem;
          padding-left: 2rem;
          }
          
        .plan-name {
          font-size: var(--dx4c-typography-heading-sm-font-size);
          font-weight: var(--dx4c-typography-heading-sm-font-weight);
          line-height: var(--dx4c-typography-heading-sm-line-height);
          height: 4.6rem;
          }
          
        .plan-sub-text {
          color: var(--dx4c-secondary-text-color);
          }
          
        .plan-price {
          display: inline-flex;
          width: 100%;
          }
          
          .plan-price-span {
          width: 50%;
          }
          
          .plan-price-amount {
          font-size: var(--dx4c-typography-heading-xl-font-size);
          font-weight: var(--dx4c-typography-heading-xl-font-weight);
          line-height: var(--dx4c-typography-heading-xl-line-height);
          }
          
          .plan-button {
          margin-left: 2rem;
          margin-top: 2rem;
          }
          
          .plan-info-container {
          height: 17.9rem;
          overflow: hidden;
          padding: 1rem 2rem 0rem 2rem;
          }
          
          .plan-feature-info {
          margin-block-end: 1rem;
          font-size: var(--dx4c-typography-body-md-font-size);
          line-height: var(--dx4c-typography-body-md-line-height);
          }
          
          .plan-description {
          width: auto;
          height: 5.25rem;
          padding: 2rem 2rem 0rem 2rem;
          display: -webkit-box;
          -webkit-line-clamp: 4 !important;
          -webkit-box-orient: vertical;
          overflow: hidden;
          font-size: var(--dx4c-typography-body-md-font-size);
          line-height: var(--dx4c-typography-body-md-line-height);
          }
          
          .plan-check-mark {
          margin-right: 0.625rem;
          width: 1.1rem;
          height: 0.72rem;
          }

Create Locale Information

You need to add any component-specific locale strings to the /locales directory under the component directory for your widget, which is /plugins/components/plan/locales in this example. Note that any shared locale strings should be put into this common resource directory.

The locales directory should contain resource bundles for all the supported locales. These resource bundles contain strings. For example, the English locale, or the en.json file, contains the following strings for the Plans widget:

{
 "subTextLabel": "Per Month",  
 "choosePlanLabel ": "Choose Plan"
}

The widget obtains localized text from the resource bundle that identifies the language of the shopper's browser. All these resources are passed into widgets as normal props (properties). You can add the Label suffix as a naming convention to distinguish these props from other props.

For example, the German locale, or the /plugins/components/plans/locales/de.json file, contains the following labels for the Plans widget:

{
  "subTextLabel": "[de]Per Month[de]",
  "choosePlanLabel ": "[de]Choose Plan[de]"
}

To pass the resources in as component props, replace the meta.js file for the widget with the following:

import de from './locales/de.json';
import en from './locales/en.json';
  
/**
 * Metadata for the Plans component.
 */
export default {
  // Include references to all of our resource strings in all supported locales.
  // This will enable the component to access any resource string via its props,
  // using the locale that is currently in effect.
  resources: {
    de,
    en
  }
};

Once you add the resources to the appropriate resource bundle file and export resources by updating the meta.js file, the subTextLabel and choosePlanLabel appear as part of the promotionOffering props object in the /plugins/components/plans/index.js file:

These props are also passed to the Plan widget as it's a child widget. Now you can see in the index.js file for your widget that you extract the two resources bundles from the promotionOffering props object and use those labels, subTextLabel and choosePlanLabel, in the Plan widget.

Note that you must redeploy your application for localization to take effect as the localization mechanism requires the Design Studio text snippets to be created to contain the translated strings.

Now that you have created your widget, you can configure it to communicate with the TM Forum API. You can also perform the following steps if the predefined resources don't match your requirement:

  • Add a new selector to the widget to access the data.

  • Write an action to access an endpoint.

  • Create an endpoint.

For more information, see Related Topics.