Components:Component Interactions - bettyblocks/cli GitHub Wiki

Component interactions enable communication between components by defining functions in either a file the component set or inside a target component of the set. These functions can subsequently be called by source components.

Interaction Types

Three types of interactions are supported at the moment:

  • Standard interactions, provided by the platform
  • Custom interactions, provided by a target component
  • Global interactions, provided by a component set

Standard Interactions

The following standard actions are provided at the moment:

  • Toggle (deprecated)
  • Login (deprecated, use the global "login" interaction instead)

Standard interactions are linked to component options. See the examples below.

Custom Interactions

Custom interactions can be defined by using the B.defineFunction helper in the JSX body of a component.

The B.defineFunction signature looks as follows: (name: string, interaction: (e: Event) => void): void.

 B.defineFunction('Increment', e => setAmount(n => n + 1));

UPDATE: You no longer have to use defineFunction inside a useEffect. Using B.defineFunction within a useEffect can cause a stale closure in some cases. E.g. You are using options inside your defineFunction that are updated dynamically. You can still use B.defineFunction from within a useEffect, this has no negative effects if it's already working for you.

B.defineFunction('Increment', e => setAmount(n => n + 1));

Make sure the root element of a component supports the triggers you want to enable, like a <form /> element for the Submit trigger and an <input /> element for the Change trigger. Wrapping these elements in a parent, like a <div /> or a <section /> element won't work. These shortcomings can be mitigated by making use of custom triggers.

Whenever you attach something like a Change or Click to call a B.defineFunction the original event will be available as the first argument in your function.

Return values are ignored at the moment.

Global Interactions

Global Interactions reside in the interactions directory of your component set, next to the components and prefabs directories.

Just like standard interactions, global interactions are linked to options.

Global interactions are written in TypeScript and look as follows:

File interactions/multiply.ts, content:

function multiply({ x, y }: { x: number; y: number }): number {
  return x * y;
}

As the name implies, this function multiplies two numbers, x and y, linked to the options of a source component. The return value is also a number, which will be linked to the option of a target component.

The following types and corresponding options are supported at the moment:

  • Type boolean connects to options of the type TOGGLE
  • Type number connects to options of the type NUMBER
  • Type string connects to options of the type TEXT and VARIABLE
  • Type Page connects to a page with the following interface:
interface Page {
  name: string;
  url: string;
}

If you use Page the interface above must be appear at the top of your interaction file as well

These types will be extended in the future, to support more kinds of options.

Besides the types mentioned above, global interactions can also recieve arguments of the type Event, like so:

function multiply({ x, y, e }: { x: number; y: number, e: Event }): number {
  console.log(e);
  return x * y;
}

These events are suplied by the trigger (click, change, ...).

Triggers

Interactions are initiated by triggers (events).

Two types of actions are supported at the moment:

  • Standard triggers, provided by the platform
  • Custom triggers, provided by a component set

Standard triggers

The following standard triggers are supported at the moment:

  • Click
  • Success
  • OnActionSuccess
  • OnActionError
  • OnActionLoad

The Success trigger is only supported in the context of the Login action. This will be expanded on in the future.

Custom Triggers

Custom triggers can be triggered by using the B.triggerEvent helper in the JSX body of a component.

The B.triggerEvent signature looks as follows: (name: string, data?: any): void.

const handleClick = event => {
  B.triggerEvent("CustomClick", event);
};

You can pass model context to your event like so

const MyComponent = () => {
  const { model } = options
  return (
    <InteractionScope model={model}>
      {context => (
         <Button onClick={(event) => B.triggerEvent("OnClick", event, context)}>click me</Button>;
      )}
    </InteractionScope>

   )
}

It is advised to use custom triggers only in combination with custom functions. In addition it is advised to use custom triggers only in the react function body, instead of inlined at JSX level, like the examples below. But of course that's the best practice for any code.

Use Options

With the useOptions React hook it is possible to mutate options inside a component:

const [, setOptions] = useOptions();

useEffect(() =>
  setOptions({
    key1: 'value1',
    key2: 'value2',
    key3: 'value3',
    ...
  }),
[]);

The options hook also supplies the options itself, but this is redundant because the options are already injected into the component implicitly:

const [opts, setOpts] = useOptions();

const setName = name => setOpts({ name });

console.log(opts);

Examples

Examples of interactions and triggers

Default Toggle

Prefab

(() => ({
  name: 'Panel',
  icon: 'PanelIcon',
  category: 'CONTENT',
  structure: [
    {
      name: 'Panel',
      options: [
        {
          type: 'TOGGLE',
          label: 'Show',
          key: 'show',
          value: true,
        },
      ],
      descendants: [],
    },
  ],
}))();

Component

(() => ({
  name: 'Panel',
  type: 'PANEL',
  icon: 'PanelIcon',
  allowedTypes: [],
  orientation: 'HORIZONTAL',
    jsx: (() => (
    <div className={options.show ? classes.show : classes.hide}>
      Some Panel...
    </div>
  ))(),
  styles: () => () => ({
    show: {
      display: 'block',
    },
    hide: {
      display: 'none',
    },
  }),
}))();

Custom Toggle

Prefab

(() => ({
  name: 'Panel',
  icon: 'PanelIcon',
  category: 'CONTENT',
  structure: [
    {
      name: 'Panel',
      options: [],
      descendants: [],
    },
  ],
}))();

Component

(() => ({
  name: 'Panel',
  type: 'PANEL',
  icon: 'PanelIcon',
  allowedTypes: [],
  orientation: 'HORIZONTAL',
  jsx: (() => {
    const [show, setShow] = useState(true);

    useEffect(() => {
      B.defineFunction('Toggle', () => setShow(s => !s));
    }, []);

    return (
      <div className={show ? classes.show : classes.hide}>
        Some Panel...
      </div>
    );
  })(),
  styles: () => () => ({
    show: {
      display: 'block',
    },
    hide: {
      display: 'none',
    },
  }),
}))();

Custom Trigger

(() => ({
  name: "Button",
  type: "BUTTON",
  icon: "ButtonIcon",
  orientation: "VERTICAL",
  allowedTypes: [],
  jsx: (() => {
    const handleClick = event => {
      B.triggerEvent("CustomClick", event);
    };
    return (
      <button onClick={handleClick} className={classes.root}>
        Button
      </button>
    );
  })(),
  styles: () => () => ({
    root: {},
  }),
}))();

Global Subtotal

Interaction

File interactions/subtotal.ts, content:

function subtotal({
  quantity,
  price,
}: {
  quantity: number;
  price: number;
}): number {
  return quantity * price;
}

Use Options

Update name with interaction on change

const [, setOptions] = useOptions();

useEffect(() =>
  B.defineFunction('UpdateName', event =>
    setOptions({
      name: event.currentTarget.value,
    }),
  ),
[]);

Custom Filter on GetAll with event

Prefab

(() => ({
  name: 'List',
  icon: 'SelectIcon',
  category: 'CONTENT',
  structure: [
    {
      name: 'List',
      options: [
        {
          type: 'MODEL',
          label: 'Model',
          key: 'model',
          value: '',
        },
        {
          type: 'PROPERTY',
          label: 'Filter property',
          key: 'filterProperty',
          value: '',
        }
      ],
      descendants: [],
    },
  ],
}))();

Component

(() => ({
  name: 'List',
  type: 'LIST',
  icon: 'SelectIcon',
  allowedTypes: [],
  orientation: 'HORIZONTAL',
  jsx: (() => {
    const { filterProperty, model } = options;
    const { GetAll } = B;

    const [filter, setFilter] = useState({});

    useEffect(() => {
      B.defineFunction('Filter', event => {
        const properties = Array.isArray(filterProperty)
          ? filterProperty
          : [filterProperty];

        const rawFilter = properties.reduceRight(
          (acc, curr) => ({
            [curr]: acc,
          }),
          { eq: event.target.value },
        );

        setFilter({
          ...filter,
          ...rawFilter,
        });
      });
    }, []);

    return (
      <div>
        <ul>
          <GetAll modelId={model} filter={filter} skip={0} take={50}>
            {({ data }) => {
              if (data && data.results) {
                return data.results.map(item => <li>{item.name}</li>);
              }

              return '';
            }}
          </GetAll>
        </ul>
      </div>
    );
  })(),
  styles: (() => ({}))(),
}))();

Put an input on the page and configure an interaction to call the Filter function onChange on this List component.

⚠️ **GitHub.com Fallback** ⚠️