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.
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
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 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 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 typeTOGGLE
- Type
number
connects to options of the typeNUMBER
- Type
string
connects to options of the typeTEXT
andVARIABLE
- 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, ...).
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
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 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.
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 of interactions and triggers
(() => ({
name: 'Panel',
icon: 'PanelIcon',
category: 'CONTENT',
structure: [
{
name: 'Panel',
options: [
{
type: 'TOGGLE',
label: 'Show',
key: 'show',
value: true,
},
],
descendants: [],
},
],
}))();
(() => ({
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',
},
}),
}))();
(() => ({
name: 'Panel',
icon: 'PanelIcon',
category: 'CONTENT',
structure: [
{
name: 'Panel',
options: [],
descendants: [],
},
],
}))();
(() => ({
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',
},
}),
}))();
(() => ({
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: {},
}),
}))();
File interactions/subtotal.ts
, content:
function subtotal({
quantity,
price,
}: {
quantity: number;
price: number;
}): number {
return quantity * price;
}
const [, setOptions] = useOptions();
useEffect(() =>
B.defineFunction('UpdateName', event =>
setOptions({
name: event.currentTarget.value,
}),
),
[]);
(() => ({
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: [],
},
],
}))();
(() => ({
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.