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.
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.
- 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.
- 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.
- 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.
- The endpoint response is applied to a specific area of the Redux store.
- 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
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.
/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 acreateClient
function, which initializes a Redux store.server.js
- This file initiates thecreateServer
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 thecreateServer
function in theserver.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 Each widget has its own component-level |
meta.js |
The Each plug-in has its own root Each widget has its own component-level |
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.
/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.
/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.
/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
.
/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"
]
}
]
}