Understand widgets

Widgets, which work together with plug-ins and other components, help you create your website user interface.

Before you can create custom widgets, ensure that your development environment is configured correctly. For detailed information on creating your environment, refer to Set Up a Development Environment.

Widgets are user interface elements of your application that work in tandem with plug-ins. Plug-ins are elements that have been configured or created to support your application. Each widget, which is also a plug-in, is an individual piece of code that performs specific tasks that interact with other plug-ins, such as actions, and can be re-used as needed. Widgets are the only component that is displayed on the Design page in the administration interface, and must be exported to be visible.

Widgets in OSF are state driven and are composed on the OSF server. Widgets be constructed from a broad spectrum of JavaScript technologies and are typically more concentrated and lightweight. OSF widgets can be grouped together into a container for easy placement on your layouts, or they can be referenced independently.

Design features with widgets

Use widgets when you want to design features that are available in different contexts on multiple pages. Widgets are also useful if you want to place an element in several locations or target specific audiences.

OSF provides several default widgets that give you the ability to interact with your customers, update and modify profile information, customize product information, and display payment and shipping methods. However, you may want to create a new widget or customize an existing widget.

For example, you might want to create a new widget that presents your shoppers with upsell information or customize an existing widget that allows your shoppers to set up a default favorite store. Or you may want to create a page where your shoppers can select a specific currency. Widgets allow you to create these interfaces.

Prerequisites for widgets

Before you create or customize widgets, you should be familiar with the technologies described in the Prerequisites section. You should also be familiar with creating and using the following:

  • Redux Store – (Information can be found at https://redux.js.org/api/store/)
  • Actions – (Information can be found at https://redux.js.org/basics/actions)
  • Sagas– (Information can be found at https://redux-saga.js.org/docs/introduction/BeginnerTutorial.html)
  • Reducers – (Information can be found at https://redux.js.org/basics/reducers/)
  • Promises – (Information can be found at https://developer.mozilla.org/enUS/docs/Web/JavaScript/Reference/Global_Objects/Promise)

You must also create a workspace in which your widgets will reside. You do this using the create-workspace tool, which is described in Set up a workspace.

Understand how widgets work

Widgets are React function components that do the following:

  • Export a function that accesses a props object that defines its properties.
  • Return a React element that is specified with the JSX syntax.
The following is an example of a very basic widget. This example shows a simple component that takes props and then returns a <div> with a text string:
import React from 'react';
const MyWidget = props => {
  return (
    <div>
      This is my widget!
    </div>
  );
 };
 export default  MyWidget;

Widgets typically display information from the Redux store. OSF provides a higher-order connect component that allows a widget to update its display when specific properties change within the Redux store. Widgets can also invoke actions, which are payloads of information that perform specific tasks and can result in updates to the Redux store.

Widgets are modules that export a function. Functions accept a props object that contains the properties of the component. This allows you to include that component in another component or page. The function returns a React element that is added to the DOM. React uses a JSX syntax that allows you to create composite elements that are expressed in an HTML-like manner.

The following is a high-level overview of how a typical widget works:
  1. A widget’s initial display is generated on the server using information that has been applied to the Redux store by a data fetcher. Fetchers determine actions that the component takes when generating the initial state and typically invoke an Oracle Commerce REST API endpoint and apply the response to the Redux store. Fetchers run on the server if the widget is rendered due to a full page request. However, if the widget is rendered while dynamically creating a layout as a result of navigation within a single-page application, the fetcher runs on the client.
  2. The widget invokes an action, for example, in response to a user gesture. An action is a function in the store’s API that accepts an action type and an optional payload containing additional information.
  3. Using information from the action, the Redux store invokes a Saga effect, resulting in an asynchronous call to an endpoint of the Oracle Commerce REST API.
  4. The endpoint response is applied to a specific area of the Redux store.
  5. The widget displays the updates that reflect that were made in the Redux store.

Before you can use a widget, you must export the widget information to your server's framework. When you call the client, meta, or server start functions, using the client, meta, or server.js files located in your application's /src directory, the system registers all of the exported sub-modules from the /plugins directory as parameters to those functions. This tells the framework of their availability. The root widget, which is a required component, is rendered as the top-level React component. There are additional components that are required by applications, including the container and the connect components. These required components are available to you by default.

Identify types of widgets

There are three different types of widgets, which are set using the type attribute in the component-level meta.js file. You should declare the type of widget you are creating based upon how you intend to use it:
  • Container – Containers are place holders on a layout. You can drag other widgets into a container, thereby grouping a number of related widgets together on the layout. You can also use containers to duplicate a set of widgets across a layout. Container widgets can be nested within other containers up to five levels. Containers identify regions on the layout that allow you to add components. Refer to Understand containers for information on containers.
  • Stacks – Stacks are like containers. The following stacks types are available: Accordion, Tab, Popup, and the Checkout Layout Progress Tracker.
  • Individual – Individual widgets are singular widgets that can be added to layouts, as well as to container or stack widgets. This is the default setting. If you do not specify a type, the widget you create is an individual widget.

Review widget file structure

Widgets have a specific directory structure. The path structure uses several files that communicate with one another to share data. Note that there are application-level files, plug-in level files, and component-specific files.

As described in Develop and Deploy Applications, when you create an application, you define specific files and file structures. In addition to creating assets, you create a /src directory, which contains a /plugins directory. This directory contains all of the components needed to work with widgets.

The /src directory should also contain the following application-level files:
  • client.js - This file provides client-side JavaScript entry points to your application. The client-side application code registers components and other entities by passing them in to a createClient function, which initializes a Redux store.
  • server.js - This file initiates the createServer function, which creates a server factory. Components and other metadata is available to your application when you provide information to the server factory.
  • meta.js - This file creates an object that contains your application's metadata, which is then passed to the createServer function in the server.js file.

The /src/plugins directory contains the code used by your widget. As an example, the currency-selector widget, located in the /my_app/src/ directory, has the following data structure and files:

    /plugins
      /actions
         index.js
         meta.js
         /get-currency
           index.js
           meta.js
      /components
         index.js
         meta.js
         /currency-selector
           index.js
           meta.js
           styles.css
           /locales
               de.js
               en.js
      /endpoints
         index.js
         meta.js
         /get-currency
            index.js
            meta.js
     

Understand widget composition

All widgets are composed of some combination of the following files. These files are laid out in the file structure specified in the description:

File name Description
index.js

Each plug-in type has its own index.js file. For example, the /package/plugins/actions/index.js file contains a dynamic import reference to the module-based actions.

Each widget has its own component-level index.js file. This /packages/plugins/component/component-name/index.js file performs a dynamic import of the helper components, classes, and plug-in-specific objects.

meta.js

The meta.js file stores metadata of the component, such as resources, configurations, actions, endpoints, fetchers and the type of component. It is best to limit meta.js file dependencies to contain only fetchers, locales, configurations, type and packageId.

Each plug-in has its own root meta.js file. This meta.js file makes a dynamic reference to the module-based plug-in directory.

Each widget has its own component-level meta.js file. This /packages/plugins/component/component-name/meta.js file identifies fetchers and actions, as well as performs a dynamic import of dependencies required for the component.

selectors.js This file contains helper methods that select the required data from the store state, inject that data into a props object, and share the data with the widget. Selectors are written at the component-level or the application-level. (Application-level selectors can be shared among multiple widgets.)
styles.css This component-level file defines widget-specific CSS styles.
config.js This component-level file provides the widget settings that are imported in the meta.js file. It is recommended that you write a config.js file that contains the necessary configuration settings and refer to the config.js file from the meta.js file.

Understand containers

You can use containers to nest components within other components. Containers, which are regular components with associated layouts, can be nested up to five levels deep. Container components are React components that provide a list of regions as part of their state model. Containers can also pass context down to child components.

The component is responsible for rendering the layout. The following is an example of a container widget’s /plugins/components/container/index.js file:
import React from 'react';
 import Region from '@oracle-cx-commerce/react-components/region';
 import Styled from '@oracle-cx-commerce/react-components/styled';
 import css from './styles.css';

 const Container = props => {
  const {regions = [], configuration = {}} = props;
  const {className = ''} = configuration || {};

  return (
    <Styled id="Container" css={css}>
      {/* render each child region */}
      <section className={`Container__Section ${className}`}>
        {regions.map(regionId => ( 
          <Region key={regionId} regionId={regionId} />
        ))}
      </section>
    </Styled>
  );
 };
 export default Container;

This example shows how the container defines regions and the container class name.

The following is an example of a container widget’s /plugins/components/container/meta.js file. The metadata for a container widget might contain information such as the following:
import config from '../../config/general/config.json';
 import de from '../../config/general/locales/de.json';
 import en from '../../config/general/locales/en.json';

 export const Container = {
  type: 'container',
  config: {
    ...config,
    locales: {
      en,
      de
    }
  }
};

Containers can also be preconfigured and uploaded with an application. This allows you to associate a predefined layout with a default container component. For example, you could upload a custom header that is comprised of nested components. Predefined containers that use predefined layouts are stored in your /assets/containers directory. Once you have uploaded a predefined container, it is available in the component drawer in the administrative interface and can be dragged onto a layout. Once it has been dragged onto a layout, the administrative interface creates a component instance that uses the associated predefined layout.

When you create a predefined container, you create the necessary JSON files in the /container directory in the root level of your application.

For example, if you were to create a /src/components/container/header.json file, it might contain the following:
{
  "title": "Header",
  "component": "Container",
  "layout": [
    {
      "width": 2,
      "components": [
        "MenuMobile"
      ]
    },
    {
      "width": 8,
      "components": [
        "Logo"
      ]
    },
    {
      "width": 1,
      "components": [
        "SearchMobile"
      ]
    },
    {
      "width": 1,
      "components": [
        "CartStatusMobile"
      ]
    }
  ]
}

The metadata of this container contains the title, base component and the layout.

Once you have created the container, you can reference it from the default page layouts, located in the /pages/home.json file:
{
  "title": "Homepage",
  "address": "/home",
  "shortName": "home",
  "layout": [{
      "type": "header",
      "width": 12,
      "components": [
        "Header" 
     ]
    },
    {
      "type": "main",
      "width": 12,
      "components": [
        "Placeholder"
      ]
    },
    {
      "type": "footer",
      "width": 12,
      "components": [
        "Footer"
      ]
    }
  ]
}