Fluent Commerce Logo
Docs
Sign In

Registering SDK Components

Topic

Author:

Fluent Commerce

Changed on:

6 Sept 2024

Overview

Developers can add new components to OMX via the registries.

There are three available:

  • Component Registry - for standard in-page components
  • Field Registry - for interactive form field components
  • Template Registry - for adding new template helpers

Registering SDK Components

Author:

Fluent Commerce

Changed on:

6 Sept 2024

Overview

Developers can add new components to OMX via the registries.

There are three available:

  • Component Registry - for standard in-page components
  • Field Registry - for interactive form field components
  • Template Registry - for adding new template helpers

Key points

  • Component Registry
  • Field Registry
  • Template Registry

Component Registry

Components are the building blocks of an OMX web app.

SDK developers can build and register new components with the Component Registry, which allows them to be referenced in a web app just like out-of-the-box components.

Naming a component

Component names don't need to follow any strict rules, but in general should be as simple as possible, while leaving room to expand later on.

The typical Fluent convention is a period-separated hierarchy of:

  • account namespace of the author (`fc.` in our case, for "Fluent Commerce")
  • the type of component (e.g. `card.`)
  • and finally the variant (e.g. `map`)

So in all, a card that shows a map would be called `fc.card.map`.

Sometimes you might have variants of variants, in which case it's fine to add levels as we have with `fc.button.print.download` and `fc.button.print.inline`.

Registering a component

New components can be installed via the ComponentRegistry.

After writing a custom component, rather than rendering it you simply give it a name in the ComponentRegistry...

1const HelloComponent: FC<HelloComponentProps> = ({label}) => {
2    return (<h2>{label}</h2>);
3}
4ComponentRegistry.register(['ACME.HelloWorld'], HelloComponent);

...and it becomes reference-able in a document:

1routes: [
2  { path:'hello', component:'Page', decendants:[
3    { component:'ACME.HelloWorld', props:{ 'label':'Hello World!' } }
4  ]}
5]

The ComponentRegistry also provide a custom extension of PropTypes that allows the framework to capture more information about the new component to do things like validating a and (later) providing a visual app builder experience. The Typescript types for React's prop-types provides an InferProps interface to avoid needing to duplicate prop configuration, e.g.

1const myProps = {
2    a: PropTypes.string(),
3    b: PropTypes.number().isRequired,
4    c: PropTypes.arrayOf.object(ColourPicker).isRequired,
5};
6const HelloProps: FC<InferProps<typeof myProps>> = ({b /* inferred as 'number' */, c /* inferred as custom type 'ColourPicker[]' */}) => {
7    return <div />;
8};
9BasicComponent.displayName = 'HelloProps';
10BasicComponent.propTypes = myProps;

Retrieving a component from the registry

To use a component from the registry you can retrieve it using the provided `get` method.

1const Comp = ComponentRegistry.get('acme.card.custom');
2return Comp ? (<Comp data={data} width={6} />) : null;

You can also ask for a set of component names and the Component Registry will return the first match. This is especially useful for allowing other SDK developers to provide implementations for a part of your component.

For example, in the filter component we use `ComponentRegistry.get()` to attempt to load a series of fallbacks to represent the filter label. The is most specific to least, allowing developers to override at the level they need to. If none are overridden, a basic `span` is returned.

1const Comp = ComponentRegistry.get([
2    `fc.filter.${entityType}.${fieldName}.label`,
3    `fc.filter.${entityType}.${fieldType}.label`,
4    `fc.filter.${fieldName}.label`,
5    `fc.filter.${fieldType}.label`,
6], { noLog:true });
7
8return Comp ? (<Comp data={filter} />) : (<span>{filter.value.toString()}</span>);

Field Registry

New form fields are installed via the Field Registry.

This allows developers to add new field components to UX forms generated either from user actions (to match the data types expected by custom Rules) or mutations (to improve the UX of previously purely auto-generated forms).

1FieldRegistry.register(['string', 'text', 'input'], TextInput);

While rendering a form, the Form component will check for field implementations to match the type, or GQL field type, and use that component in that position in the form.

The form component is responsible for implementing the necessary interface to make it work within a form, which are enforced by the Typescript interfaces the developer must inherit from to register the component in the first place. See the TSDocs for more details.

Extending Filters

The standard `fc.list` component has a filter bar driven by the schema.

Using the Field Registry, you can implement custom UX for the configuration and display of these filters.

Adding a new filter type

New filter types can be registered via the FieldRegistry. The field component should behave the same way as a normal form field (i.e. render a control and produce values via onChange), but it will be shown inside a modal when a matching filter is selected from the filters dropdown.

Common use-cases include:

  • implementing a UX for a GraphQL type that doesn't currently exist
  • tailoring the UX of an existing filter for a special case
  • using the `useSetting` hook inside a filter component to pre-fetch a list of valid type or status value from a retailer or account setting

The UI component that's chosen to represent a query parameter follows a fallback pattern based on the field type and name in the schema, from most specific to least. So in the case of an status filter, the following field aliases will be checked in :

  • `fc.filter.order.status`
  • `fc.filter.order.string`
  • `fc.filter.status`
  • `fc.filter.string`

Using the OMX SDK, simply register a field component by the right name to add or replace a filter.

Custom filter labels

When a filter produces a non-string result it might not look right in the filter chip that is produced on submit.

For these cases, you can use the ComponentRegistry to provide a view component that will be embedded within the chip, following the same fallback logic:

  • `fc.filter.order.status.label`
  • `fc.filter.order.string.label`
  • `fc.filter.status.label`
  • `fc.filter.string.label`

Filter label components will receive the value of the filter (the one produced by the `onChange`  in the field) as the `data` prop, and should be registered with the ComponentRegistry:

1ComponentRegistry.register(['fc.filter.order.status.label'], OrderStatusWithIcon, {category:'filter'});

Extending Mutations

When a form is generated for a mutation, chooses fields based on a similar specificity-based fallback strategy.

  • `fc.mutation.order.attribute.value` (full path and field name)
  • `fc.mutation.order.attribute.json` (full path and field type)
  • `fc.mutation.attribute.value` (entity type and field name)
  • `fc.mutation.attribute.json` (entity type and field type)
  • `fc.mutation.value` (field name)
  • `fc.mutation.json` (field type)

This allows SDK developers to override the default field implementation either across the board (e.g. all `string` fields) down to very specifically (e.g. the `value` field of `order attributes`) by registering a new field with the `FieldRegistry` at the desired level.

Template Registry

Many OMX components use template strings to allow customisation of components via the .

1{ "value": "{{order.status}}" } // = "BOOKED"

Template helpers allow configurers to transform data in different ways, like changing uppercase text to mixed case, or converting an absolute timestamp to a relative one.

1{ "value": "{{dateRelative order.createdOn}}" } // = "5 days ago"

The Template Registry allows developers to add to the standard list of helpers available.

1TemplateRegistry.register(['uppercase', 'toUpperCase'], function(str) {
2    return str.toUpperCase();
3});

Helper functions should be in the format defined by the Handlebars framework documentation.

Fluent Commerce

Fluent Commerce