Bi-designer is the front-end construction engine developed by the Alibaba Data Center team. Based on it, we have developed the largest data analysis platform inside Ali and QuickBI on Ali Cloud.
Bi-designer is not open source at present, so the private NPM source @alife/ Bi-Designer used in this article is not accessible on the public network.
This article introduces the USE API of the Bi-Designer component.
The component loading
Component instances are defined in the meta-information – Element:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
element: () = > <div />};Copy the code
Asynchronous loading
Use react. lazy to load components asynchronously:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
/ / lazy loading
element: React.lazy(async() = >import("./real-component")),};Copy the code
Lazy components are automatically loaded. To customize Loading effects, you can read the asynchronous and error handling documents of components.
Component asynchrony, error handling
- Asynchronous loading component source code, or to take the number of Suspense, invoked ComponentMeta. SuspenseFallback rendering.
- The component rendering when something goes wrong, will call ComponentMeta. ErrorFallback rendering.
Asynchronous loading
import { Interfaces } from "@alife/bi-designer";
const SuspenseFallback: Interfaces.InnerComponentElement = ({ componentInstance, componentmeta, }) = > {
return <span>Loading</span>;
};
const componentMeta = {
componentName: "suspense-custom-fallback".element: React.lazy(async() = > {await sleep(2000);
return Promise.resolve({ default: () = > null });
}),
suspenseFallback,
};
Copy the code
In the example above, the suspenseFallback is defined for the component loaded asynchronously to handle the state in asynchrony.
Error handling
import { Interfaces } from "@alife/bi-designer";
const errorFallback: Interfaces.ErrorFallbackElement = ({ componentInstance, componentmeta, error, }) = > {
return <span>Error: {error. The toString ()}</span>;
};
const componentMeta = {
componentName: "error-custom-fallback".element: () = > {
throw new Error("error!");
},
errorFallback,
};
Copy the code
In the example above, errorFallback handles any errors thrown by the component.
- Error: Indicates an error message of the current component.
Container components
Container elements can be dragged into child elements by setting isContainer to true:
export const yourComponentMeta: Interfaces.ComponentMeta = {
componentName: "yourComponent".element: YourComponent,
isContainer: true};Copy the code
You can then access the child element from props. Children:
const YourComponent = ({ children }) = > {
return <div>{children}</div>;
};
Copy the code
Multi-slot container components
A multi-slot container is a container that has multiple locations within it that can respond to drag and drop.
To implement multi-slot container components, note two points:
- The large container component itself is not a container type because we are dragging into a child element, not into itself.
- Internally add container class components as child elements via ComponentLoader.
For example, if we want to implement a multi-slot container with Antd Card, first declare Card as a common component:
export const cardComponentMeta: Interfaces.ComponentMeta = {
componentName: "card".element: CardComponent,
};
Copy the code
To implement Card, we call ComponentLoader in two internal draggable areas to load a predefined container component div:
import { ComponentLoader, useDesigner } from '@alife/bi-designer'
const CardComponent: Interfaces.ComponentElement = () = > {
const { useKeepComponentLoaders } = useDesigner()
useKeepComponentLoaders(['1'])
return (
<Card
actions={[...]. }
>
<ComponentLoader
id="1"
componentName="div"
props={{style: { minHeight: 30}}} / >
</Card>
);
};
Copy the code
To summarize, you can use ComponentLoader to load any component inside a component. If you load a container component, you add an internal slot. Such slots can be inserted into a theoretically infinite number of container components, depending on the business requirements, such as the simplest div container above, which can be implemented like this:
const Div: Interfaces.ComponentElement = ({ children, style }) = > {
return (
<div style={{ width: "100% ",height: "100% ",. style}} >{children}</div>
);
};
Copy the code
Tabs container component
The Tabs container can be thought of as a dynamic number of multi-slot containers:
import { ComponentLoader, useDesigner } from "@alife/bi-designer";
const TabsComponent: Interfaces.ComponentElement = ({ tabs }) = > {
const{ useKeepComponentLoaders } = useDesigner(); useKeepComponentLoaders(tabs? .map((each) = > each.key));
return (
<div>
<Tabs>{tabs? .map((each) => (<Tabs.TabPane tab={`TabThe ${each.title} `}key={each.key}>For example, take the div component as a container for TabPane */<ComponentLoader id={each.key} componentName="div" />
</Tabs.TabPane>
))}
</Tabs>
</div>
);
};
Copy the code
Tabs dynamically renders TabPanes based on configuration, filling each TabPane with a container.
Note that the useKeepComponentLoaders function allows you to clean up dirty data on the canvas when a subtab disappears after a data change. In addition, the function should be updated even if the data is not dynamic. For example, when updating, the value of 3 ComponentLoader is removed from the code, and the 3 id is removed from useKeepComponentLoaders.
High component wide
For components with adaptive height, the best solution is to set the width to 100% :
import { Interfaces } from "@alife/bi-designer";
const CustomComponent: Interfaces.ComponentElement = () = > {
return <div style={{ width: "100% ",height: "100% ",minHeight: 50}} / >;
};
Copy the code
Height: ‘100%’ height will collapse, so you can either set a minimum fixed height or let the user configure it via props.
If a component does not support adaptive width and height, for example, when rendering a canvas or SVG diagram, you need to monitor the width and height by yourself, or extend the component props function by using a container, calculate the specific width and height value in the container, and then pass it to the component.
Of course, you can also directly set a default height, or according to the content of the dynamic components, in streaming layout, tile layout can be automatically opened container (tile layout edit mode drag height is allowed to be run automatically extended), in free layout cannot be opened, there will be an inner scroll bar.
Component configuration defaults
The default values for the component configuration form are defined in ComponentMeta.props:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
props: [{name: "title".defaultValue: "Title",}]};Copy the code
Props describes the component input parameter information, including:
interface MetaProps {
/** * Attribute name */
name: string;
/** * Attribute type */
type? :string;
/** * Attribute description */description? :string;
/** * Default */defaultValue? :any;
}
Copy the code
If you only set default values, you only need to care about name and defaultValue.
Component configuration form
Component configuration form in ComponentMeta. PropsSchema defined in:
import { Interfaces } from '@alife/bi-designer'
const componentMeta: Interfaces.ComponentMeta = {
platform: 'fbi'.// Platform name
propsSchema: {
style: {
color: {
title: 'Color'.type: 'color'.redirect: 'color',},},},}Copy the code
- Platform: indicates the project type. The propsSchema structure may differ from item type to item type, as may other fetch logic.
- PropsSchema: form configuration structure, which conforms to UISchema specification. You may use your own specification for special forms.
Component configuration modification callback
Component configuration changes in each component instance callback information is modified when triggered, in ComponentMeta. OnPropsChange defined in:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
onPropsChange: ({ prevProps, currentProps, componentMeta }) = > {
return {
...currentProps,
color: "red"}; }};Copy the code
- PrevProps: Last component configuration.
- CurrentProps: Current component configuration.
- ComponentMeta: componentMeta information.
- Return: New component configuration.
Associate configuration updates across components
When the canvas any component changes, component can in ComponentMeta. OnPageChange listening to, and modify the component configuration:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
onPageChange: ({ props, pageSchema }) = > {
// Empty when the associated component cannot be found
if(! pageSchema? .componentInstances?.some((each) = > each.id === props.value)
) {
return {
...props,
/ / to empty props. The value
value: ""}; }// The return value updates the current component configuration
returnprops; }};Copy the code
- Props: Current component configuration.
- PageSchema: Page information.
- Return: New component configuration.
If the component is configured with other component ids, the onPageChange callback can be used to determine if the target component does not exist and update part of the configuration of the current component.
Components are hidden
Component hiding can be set with hide:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
hide: ({ componentInstance, mode }) = > true};Copy the code
- ComponentInstance: indicates the componentInstance information.
- Mode: current mode, such as component only edit mode hidden, can be checked ({mode}) => mode === ‘edit’.
Attribute value type – JSSlot
JSSlot is a configuration type that allows you to set one of the props parameters of a component to another component instance and pass it as a React Node at runtime.
import { Interfaces } from "@alife/bi-designer";
// The component uses props. Header directly as JSX
const ComponentWithJSSlot: Interfaces.ComponentElement = ({ header }) = > {
return (
<div>Header element: {header}</div>
);
};
// Add Slot description to DSL
const defaultPageSchema: Interfaces.PageSchema = {
componentInstances: {
tg43g42f: {
id: "tg43g42f".componentName: "js-slot-component".index: 0.props: {
header: {
type: "JSSlot".value: ["child1"."child2"],}}},child1: {
id: "child1".componentName: "input".parentId: "tg43g42f".index: 0.isSlot: true,},child2: {
id: "child2".componentName: "input".parentId: "tg43g42f".index: 1.isSlot: true,}}};Copy the code
- IsSlot: indicates that the node type is JSSlot.
- Type: ‘JSSlot’ : Indicates that the tag attribute is of type JSSlot and the value array stores Slot component ids.
Property value type – JSFunction
JSFunction is a configuration type that allows you to set a component props parameter to a custom function.
import { Interfaces } from "@alife/bi-designer";
// The component directly uses props. OnClick as a function call
const FunctionComponent: Interfaces.ComponentElement = ({ onClick }) = > {
return <div onClick={onClick} />;
};
// Add Function description to DSL
const defaultPageSchema: Interfaces.PageSchema = {
componentInstances: {
test: {
id: "tg43g42f".componentName: "functionComponent".index: 0.props: {
onClick: {
type: "JSFunction".value: 'function onClick() { console.log("123") }',},},},},};Copy the code
- Type: ‘JSFunction’ : Indicates that the attribute is of type JSFunction and value stores the function body as a string. Function can be extended using context data objects and utility classes.
Attribute value type – JSExpression
JSExpression is a configuration type that allows you to set a component props parameter to a custom expression.
import { Interfaces } from "@alife/bi-designer";
// The component is rendered directly using props. Variable
const ExpressionComponent: Interfaces.ComponentElement = ({ variable }) = > {
return <div>JSExpression: {variable}</div>;
};
// Add Expression description to DSL
const defaultPageSchema: Interfaces.PageSchema = {
componentInstances: {
test: {
id: "tg43g42f".componentName: "expressionComponent".props: {
variable: {
type: "JSExpression".value: '" 1 "+" 2",},},},},};Copy the code
- Type: ‘JSExpression’ : The label attribute is of the JSExpression type, and value stores expressions using strings. Expressions can be extended using context data objects, and utility classes.
Component state persistence
The component itself can persist state to the configuration at run time through the updateComponentById function:
import { Interfaces, useDesigner } from "@alife/bi-designer";
import * as fp from "lodash/fp";
const componentMeta: Interfaces.ComponentMeta = {
element: Component,
};
const Component: Interfaces.ComponentElement = ({ id, count }) = > {
const { updateComponentById } = useDesigner();
const handleIncCount = React.useCallback(() = > {
updateComponentById(id, (each) = >
fp.set("props.count", each? .props? .count +1, each)
);
}, [id, updateComponentById]);
return <div onClick={handleIncCount}>{count}</div>;
};
Copy the code
Note: Because updateComponentById modiates the canvas DSL, this DSL cannot be persisted in non-edit mode. For dirty data cleaning problems in this mode, the same as component configuration is corrected.
Dynamically creating components
Any other component can be created dynamically within a component via props.ComponentLoader:
import { Interfaces, useDesigner, ComponentLoader } from "@alife/bi-designer";
const Card: Interfaces.ComponentElement = () = > {
const { useKeepComponentLoaders } = useDesigner();
useKeepComponentLoaders(["1"]);
return (
<ComponentLoader id="1" componentName="button" props={{ color: "red}} "/ >
);
};
Copy the code
- UseKeepComponentLoaders: Synchronizes with the component IDS created dynamically for the engine to manage dynamic components. Parameter Description ComponentLoader
- Id: unique ID of a dynamic component. The DYNAMIC component ID must be unique within a component.
- ComponentName: indicates the componentName.
- Props: component props. This parameter is optional.
Dynamic component nesting
Dynamic components can be nested arbitrarily:
import { Interfaces, useDesigner, ComponentLoader } from "@alife/bi-designer";
const Card: Interfaces.ComponentElement = ({ ComponentLoader, useKeepComponentLoaders, }) = > {
const { useKeepComponentLoaders } = useDesigner();
useKeepComponentLoaders(["1"."2"]);
return (
<ComponentLoader id="1" componentName="div">This is the child element:<ComponentLoader id="2" componentName="button" />
</ComponentLoader>
);
};
Copy the code
Component configuration not rendered when not Ready
Interception of component rendering can be done at the component container or generic container layer, for example, to determine if certain configurations are not satisfied, and to show a pocket map instead of rendering the component directly:
import { Interfaces, useDesigner } from "@alife/bi-designer";
import * as fp from "lodash/fp";
const componentMeta: Interfaces.ComponentMeta = {
element: Component,
container: Container,
};
const Container: Interfaces.InnerComponentElement = ({ componentInstance, children, }) = > {
if(! componentInstance? .props? .count) {// Render conditions are not met
return <div>Count configuration not ready, do not render component</div>;
}
// Render children, which is the component itself
return children;
};
Copy the code
Do not fetch the number when the configuration is not Ready
GetFetchParam suspends fetching as long as it throws an exception:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
getFetchParam: ({ componentInstance, componentMeta, filters, context }) = > {
if(componentInstance.props? .count ! = ="5") {
// count is not '5'
throw Error("Not Ready");
}
return "123"; }};Copy the code
FetchError this error can be accessed via props. FetchError, which can be intercepted by both component and container layers:
import { Interfaces } from "@alife/bi-designer";
class PropsNotReadyError extends Error {}
const componentMeta: Interfaces.ComponentMeta = {
getFetchParam: ({ componentInstance, componentMeta, filters, context }) = > {
if(componentInstance.props? .count ! = ="5") {
throw PropsNotReadyError("Not Ready");
}
return "123";
},
container: Wrapper,
};
const Wrapper: Interfaces.InnerComponentElement = ({ componentInstance }) = > {
if (componentInstance.props.fetchError instanceof PropsNotReadyError) {
return <div>It does not satisfy the number selection condition</div>; }};Copy the code
Components should be
Whether a component initializes a fetch is defined in ComponentMeta.initFetch; Generated access parameters in ComponentMeta. GetFetchParam defined; The component fetch function is defined in ComponentMeta.fetcher
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
// Whether the component enables automatic fetch, which is triggered when the fetch parameter changes (getFetchParam control)
autoFetch: ({ componentInstance, componentMeta }) = > true.// Whether the component takes the number by default. This takes effect only when autoFetch is true
initFetch: ({ componentInstance, componentMeta }) = > true.// assemble the fetch parameters
getFetchParam: ({ componentInstance, componentMeta, filters, context }) = > {
return { name: componentInstance? .props? .name }; },// take the number function
fetcher: async ({ componentMeta, param, context }) => {
// Filters are counted based on the current component information (componentInstance) and filter conditions (value filters)
return awaitcustomFetcher(param.name); }};Copy the code
- ComponentInstance: information about the current componentInstance.
- GetFetchParam: Callback to start fetch, used to assemble fetch parameters. Returning null or undefined does not trigger fetch.
- Filters: Filtering information that applies to this component, as further described in the component filtering document. Contains the following keys:
- ComponentInstance: filters conditional componentInstance information.
- FilterValue: indicates the current filterValue of the filter criterion.
- Payload: user-defined transmission parameters defined by eventConfigs that are filtered by components. For details, see the documentation Component filter – transmits additional filtering information.
- Context: access to useDesigner everything.
After performing the fetch configuration, the component can access the data via props:
import { useDesigner } from "@alife/bi-designer";
const NameList: Interfaces.ComponentElement = () = > {
const { data, isFetching, isFilterReady } = useDesigner();
if(! isFilterReady) {return <Spin>The filter condition is not Ready</Spin>;
}
if (isFetching) {
return <Spin>Take the number of</Spin>;
}
return (
<div>
{data.map((each: any, index: number) => (
<div key={index}>{each}</div>
))}
</div>
);
};
Copy the code
- Data Indicates the result of taking a number.
- IsFetching whether fetching isFetching.
- IsFilterReady Specifies whether the filter condition is Ready. This condition is understood as a special Hold state.
- FetchError Indicates an error in obtaining the number.
You can also configure global component fetch configurations at the engine layer, which take precedence over those at the engine layer.
The component actively fetches the number
FetchData allows you to actively fetch numbers:
const NameList: Interfaces.ComponentElement = ({ fetchData }) = > {
const { fetchData } = useDesigner();
return <button onClick={fetchData}>Take the initiative to take the number</button>;
};
Copy the code
- FetchData: An active fetch function that can be refetched immediately after being called.
After an active fetch call, the fetch result is still returned through props. Data.
Custom fetch parameters
FetchData can be passed as the getFetchParam custom fetch parameter:
const NameList: Interfaces.ComponentElement = ({ fetchData }) = > {
const { fetchData } = useDesigner();
const handleFetchData = React.useCallback(() = > {
fetchData({
getFetchParam: ({ param, context }) = > ({
...param,
top: 1,})}); }, [fetchData]);return <button onClick={handleFetchData}>Take the initiative to take the number</button>;
};
Copy the code
Note that even if the fetch parameter is modified in independent fetch mode, the next externally triggered fetch will reset the fetch parameter.
Independent access
The standalone fetch can be declared by the standalone parameter, where triggering the fetch does not cause the component to Rerender and fetch the new data, but returns a Promise for the component to process.
const NameList: Interfaces.ComponentElement = ({ fetchData }) = > {
const { fetchData } = useDesigner();
const handleFetchData = React.useCallback(async() = > {const data = await fetchData({
standalone: true});// The component handles the fetch result data itself
}, [fetchData]);
return <button onClick={handleFetchData}>Take the initiative to take the number</button>;
};
Copy the code
This independent fetch scenario can be adapted to the drill-down scenario where components fetch data freely.
It can also be used with getFetchParam in standalone mode.
Actively cancel the fetch
CancelFetch cancelFetch cancelFetch cancelFetch
const NameList: Interfaces.ComponentElement = ({ cancelFetch }) = > {
const { cancelFetch } = useDesigner();
return <button onClick={cancelFetch}>Cancel the access</button>;
};
Copy the code
- CancelFetch: Cancels the fetch function, effective immediately. It has no effect if it is called after the number is fetched.
Optimize fetch performance
The value of getFetchParam is determined by whether the value returned by getFetchParam has changed. The default is deepEqual:
import { Interfaces } from "@alife/bi-design";
const componentMeta: Interfaces.ComponentMeta = {
getFetchParam: ({ componentInstance }) = > {
// The engine does a deep comparison of the return values
return { name: componentInstance?.props?.name };
},
};
Copy the code
But there are two situations that can cause performance problems:
- The return value data structure is very large, resulting in a significant increase in frequent deepEqual overhead.
- The logic of generating fetch parameters is inherently time-consuming, resulting in the overhead of frequently executing the getFetchParam function itself.
We provide an optimization for this situation, using shouldFetch to actively prevent unnecessary fetches, with the specific reference component preventing automatic fetches.
Component fetch event hook
If you want to do some updates after fetching, but don’t want to trigger additional rerendering, you can do this in the Component fetching event hook.
And then we’re done
The afterFetch hook executes after the fetch is complete:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
afterFetch: ({ data, context, componentInstance }) = > {
context.updateComponentById(componentInstance.id, (each) = >
fp.set("props.value"."newValue", each) ); }};Copy the code
- Data: The result of the fetch, that is, the return value of fetcher.
- Context: indicates the context.
- ComponentInstance: indicates the componentInstance information.
- ComponentMeta: componentMeta information.
Stream change events triggered by fetch hooks (such as updateComponentById) do not trigger additional rerenders, and their rendering timing is merged with post-fetch timing.
The component periodically fetches data
You can configure autoFetchInterval to implement the automatic data fetch function for real-time data that needs to be refreshed periodically.
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
autoFetchInterval: () = > 1000};Copy the code
- AutoFetchInterval: interval for automatic refetching, expressed in ms. If this parameter is not set, this function is unavailable.
Component force fetch
Normally, the fetch is performed only when the fetch parameter changes. However, if there is a demand to fetch the number forcibly, you can execute forceFetch:
import { useDesigner } from "@alife/bi-designer";
export default() = > {const { forceFetch } = useDesigner();
// Specify a component to recalculate
// forceFetch('jtw4x8ns')
};
Copy the code
- ForceFetch: A function that forces the number and takes the component ID as an argument.
Component selection
Trigger filter behavior
Any component can be used as a filter, as long as the implementation of onFilterChange interface has the filter capability, through the filterValue can get the current component filterValue, below create a filter function component:
import { useDesigner } from "@alife/bi-designer";
const SelectFilter = () = > {
const { filterValue, onFilterChange } = useDesigner();
return (
<span>
<Select value={filterValue} onChange={onFilterChange}>
// ...
</Select>
</span>
);
};
Copy the code
A component that triggers onFilterChange is considered to trigger a filter, and the component that triggers onFilterChange triggers a component fetch.
Set any key through an expression
Note that onFilterChange and filterValue can be mapped to any key of the component, as defined below:
{
props: {
onChange: {
type: "JSExpression".value: "this.onFilterChange"
},
value: {
type: "JSExpression".value: "this.filterValue"}}}Copy the code
The component props. OnChange and props. Value have the onFilterChange and filterValue capabilities.
The component that sets filtering
So how do you define the component being acted on? Because filtering associations are run-time capabilities, we need to use component run-time configuration capabilities.
Runtime capacity, screening of correlation function belongs to ComponentMeta. EventConfigs filterFetch part ability, namely the scope of filter condition, in the list of the components in the trigger in the current component onFilterChange triggered when access:
import { Interfaces, createComponentInstancesArray } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
eventConfigs: ({ componentInstance, pageSchema }) = >createComponentInstancesArray(pageSchema? .componentInstances)// Find all the name-list components in the canvas? .filter((each) = > each.componentName === "name-list")
?.map((each) = > ({
// The event type is filter trigger fetch
type: "filterFetch".// The condition is triggered by the current component
source: componentInstance.id,
// Applies to the found name-list component
target: each.id,
})),
};
Copy the code
Componentinstance. props = componentInstance.props = componentInstance.props = componentInstance.props = componentInstance.props = componentInstance.props = componentInstance.props
Similarly, conditional reverse binding can be implemented by setting source, which is the component that triggers onFilterChange, and target, which is the component that fetches the number.
Note: componentInstances contains all components, including its own component and root node. If you want to bind all components, you generally need to exclude its own component and root node.
{
eventConfigs: componentInstances? .filter(// Deselect the root node
(each) = >each.componentName ! = ="root" &&
// Do not select yourself
each.componentId === componentInstance.id
);
// ...
}
Copy the code
Pass additional filtering information
Bi-designer supports many-to-many with duplicate source and target, for example:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
eventConfigs: ({ componentInstance, pageSchema }) = >[{type: "filterFetch".source: componentInstance.id,
target: 1.payload: "Acting on a number parameter"}, {type: "filterFetch".source: componentInstance.id,
target: 1.payload: "Applied to field filter",}]};Copy the code
In this example, we can bind the current component to multiple targets in a row. To differentiate the effects, we can declare the payload, which is passed to the target component’s getFetchParam.filters parameter. It can be accessed through eachfilter. payload. For details, see Document Component Fetch.
There are few scenarios where the same component is continuously bound to multiple same target components, but there are many scenarios where the configuration of component A is bound to B, and the configuration of component B is bound to A.
Screening of depend on
The dependencies that exist between filter conditions are called filter dependencies.
Filter Ready dependencies
The filterReady dependency is defined by filterReady:
import { Interfaces, createComponentInstancesArray } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
eventConfigs: ({ componentInstance, pageSchema }) = >createComponentInstancesArray(pageSchema? .componentInstances)// Find all input components in the canvas? .filter((each) = > each.componentName === "input")
?.map((each) = > ({
type: "filterReady".source: each.id,
target: componentInstance.id,
})),
};
Copy the code
Target depends on source, and when the filter condition source changes, the target component’s filter is invalidated and null.
- Source: Once onFilterChange is triggered.
- Target: Component filter Ready is set to false and filterValue is set to null.
Filtering Value dependencies
FilterValue dependencies are defined by filterValue:
import { Interfaces, createComponentInstancesArray } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
eventConfigs: ({ componentInstance, pageSchema }) = >createComponentInstancesArray(pageSchema? .componentInstances)// Find all input components in the canvas? .filter((each) = > each.componentName === "input")
?.map((each) = > ({
type: "filterValue".source: each.id,
target: componentInstance.id,
})),
};
Copy the code
Target depends on source, and when the filter source changes, the filterValue of the target component will be assigned to the filterValue of from.
- Source: Once onFilterChange is triggered.
- Target: the component filterValue is set to the value of the source component filterValue.
Component filter defaults
By default, the component filter defaults to undefined, and subsequent filter changes are controlled by the component onFilterChange behavior (see the component filter documentation for details).
But if you configure filter defaults, or default from URL parameters, etc., to allow component filters to have default values, this requirement is also very reasonable, as defined by defaultFilterValue:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
// The component filters the defaults
defaultFilterValue: ({ componentInstance }) = >
componentInstance.props.defaultFilterValue,
};
Copy the code
Note that this parameter is the default value of the filter criteria. Subsequent changes of the filter criteria are not controlled by this parameter.
Component theme Style
Components can read theme style configurations in two ways:
- JS: read by, for example, props. Theme. PrimaryColor.
- CSS: Read data by using, for example, var(–primaryColor).
JS model
import { themeSelector, useDesigner } from "@alife/bi-designer";
const Component: Interfaces.ComponentElement = () = > {
const { theme } = useDesigner(themeSelector());
return <div style={{ color: theme?.primaryColor}} >The text</div>;
};
Copy the code
The CSS model
import "./index.scss";
const Component: Interfaces.ComponentElement = () = > {
return <div className="custom-text">The text</div>;
};
Copy the code
.custom-text {
color: var(--primaryColor);
}
Copy the code
The Key of the CSS schema is exactly the same as the Key of the JS variable.
Component internationalization
Component configuration uses internationalization via JSExpression:
const defaultPageSchema: Interfaces.PageSchema = {
componentInstances: {
test: {
id: "tg43g42f".componentName: "expressionComponent".props: {
variable: {
type: "JSExpression".value: 'this. I18n/" China "",},},},},};Copy the code
Internationalized content is accessed by key through this.i18n.
- Internationalization content Configuration – Configure internationalization.
- JSExpression Description – JSExpression.
Component configuration revision
When the component instance version is lower than the latest version number, a rollback has occurred, and the rollback will be corrected in sequence.
Note: For components that need to consider data rollback, the undo logic should be written well before release and put online in advance after testing. After that, the project should be officially put online to ensure that undo can be executed correctly after rollback.
Component configuration revision is defined in ComponentMeta. Revises:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
revises: [{version: 1.redo: async (prevProps) => {
return prevProps;
},
undo: async (prevProps) => {
returnprevProps; }},]};Copy the code
- Version: indicates the revised version number.
- Redo: Upgrade to this version of the revision logic.
- Undo: Go back to this version of the correction logic.
- Return: New component props.
Component top absorption
Global suction dome
Component topping is defined by ComponentMeta.fixTop:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
fixTop: ({ componentInstance }) = > true};Copy the code
- The top can be fixed by configuring fixTop without additional component support.
- If the top component has a screening function, it still has a screening function after the top.
Assembly internal suction top
Through ComponentMeta. FixTopInsideParent to set the component within the parent container roof.
- Smooth rolling cancel: set ComponentMeta smoothlyFadeOut can achieve the effect.
- Simply return the component to its original position: no configuration is required.
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
fixTop: () = > true.fixTopInsideParent: () = > true.smoothlyFadeOut: () = > true};Copy the code
Set custom styles for ceiling components
Set ComponentMeta. GetFixTopStyle customize components after suction a top style, general Settings zIndex.
type getFixTopStyle = (componentInfo: {
componentInstance: ComponentInstance;
componentMeta: ComponentMeta;
dom: HTMLElement;
context: any;
}) = > React.CSSProperties;
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
getFixTopStyle: () = > ({
zIndex: 1000000,})};Copy the code
Component rendering complete flag
The default component does not need to actively report after rendering. The following is the automatic reporting mechanism:
- When initFetch is false, the component DOM Ready is used as the render completion time.
- When initFetch is true, the component is finished fetching and DOM Ready is used as the rendering completion time.
Proactively report rendering completion signs
For special components, such as when DOM rendering is done but not when loading is done, active reporting can be selected:
import { Interfaces, useDesigner } from "@alife/bi-designer";
const customOnRendered: Interfaces.ComponentElement = () = > {
const { onRendered } = useDesigner();
return <div onClick={onRendered}>This component isn't rendered until you click on me</div>;
};
const customOnRenderedMeta: Interfaces.ComponentMeta = {
manualOnRendered: true};Copy the code
- ManualOnRendered: Disabled when true.
- OnRendered: The component is rendered, and rendered for the first time.
Component prevents automatic fetch
For scenarios where we need to fine-tune fetching timing, we can use shouldFetch to control component fetching timing:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
shouldFetch: ({ prevComponentInstance, nextComponentInstance, prevFilters, nextFilters, componentMeta, context, }) = > true};Copy the code
ShouldFetch returns false to prevent automatic fetchparam and fetcher from being executed.
- PrevComponentInstance: Information about the last component instance.
- NextComponentInstance: information about the nextComponentInstance.
- PrevFilters: Information about the last filter criteria.
- NextFilters: Information for the next filter condition.
- ComponentMeta: componentMeta information.
- Context: indicates the context.
If the fetch parameter does not change, you still need to fetch the number again. Refer to the component to force the fetch.
- ShouldFetch does not block mandatory fetching, automatic fetching, or active fetching.
- ShouldFetch blocks initFetch=true to initialize the fetch.
Component fetch as required
By default, bi-Designer fetchings are fully concurrent, meaning that the fetchings are the first to occur regardless of whether the component is in the visible area, but the result of the fetchings will not cause a refresh of the components in the non-visible area.
FetchOnlyActive: true fetchOnlyActive: true fetchOnlyActive: true fetchOnlyActive: true fetchOnlyActive: true fetchOnlyActive: true
const componentMeta = {
componentName: "line-chart".fetchonlyActive: () = > true};Copy the code
When this function is enabled for a component:
- The component initiates an automatic count only when it is in the viewable area.
- When a component appears in a visible area from a non-visible area, a fetch is automatically initiated if needed.
Component callback event
Component callback can trigger events, through the runtime configuration ComponentMeta. EventConfigs callback definitions:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
eventConfigs: ({ componentInstance, pageSchema }) = >[{type: "callback".callbackName: "onClick",}]};Copy the code
- CallbackName: the name of the callback function.
After defining the callback timing, we can trigger some actions to implement the custom effects. In the following sections, update the Props, update the component configuration, and update the fetch parameters.
Event – Update component Props
Update component configuration belongs to the Action setProps:
import { Interfaces } from '@alife/bi-designer'
const componentMeta: Interfaces.ComponentMeta = {
eventConfigs: ({ componentInstance, pageSchema }) = > [{
type: 'callback'.callbackName: 'onClick'.source: componentInstance.id,
target: componentInstance.id
action: {
type: 'setProps'.setProps: (props, eventArgs) = > {
return {
...props,
color: 'red'}}}}]}Copy the code
As configured above, the effect is to set props. Color to red.
EventArgs is an event parameter, such as onClick, called as follows:
props.onClick("jack".19);
Copy the code
setProps: (props, eventArgs) = > {
return {
...props,
name: eventArgs[0].age: eventArgs[1]}; };Copy the code
If there are multiple events acting on the setProps of the same component, the setProps function will fire multiple times in sequence.
Event – Updates the fetch parameter
Update component fetchParam for Action setFetchParam:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
eventConfigs: ({ componentInstance, pageSchema }) = >[{type: "callback".callbackName: "onClick".action: {
type: "setFetchParam".setFetchParam: (param, eventArgs) = > {
return {
...param,
count: true}; },},},],};Copy the code
The effect of the above configuration is to add count: true to the fetch parameter.
Event – Updates the filter criteria
The update filter belongs to Action setFilterValue:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
eventConfigs: ({ componentInstance, pageSchema }) = >[{type: "callback".callbackName: "onClick".action: {
type: "setFilterValue".setFilterValue: (filterValue, eventArgs) = > {
return "uv"; },},},],};Copy the code
As configured above, the effect is to change the filter condition value of the target component to UV.
conclusion
The above is a combination of general build and BI features of build engine support for component functions, if you have any questions or suggestions on the function, or API, please feel free to contact me.
The discussion address is: intensive reading “Data builder Engine Bi-Designer API-Component” · Issue #269 · dT-fe /weekly
If you’d like to participate in the discussion, pleaseClick here to, with a new theme every week, released on weekends or Mondays. Front end Intensive Reading – Helps you filter the right content.
Pay attention to the front end of intensive reading wechat public account
Copyright Notice: Freely reproduced – Non-commercial – Non-derivative – Remain signed (Creative Commons 3.0 License)