This example will create a Timeline block based on ant-design Timeline component, and before creating the block, select Time Field and Title Field.
This example is mainly to demonstrate the use of initializer. For more information about block extension, please refer to the Block Extension documentation.
The complete example code for this document can be found in plugin-samples.
Following the Write Your First Plugin documentation, if you don't have a project yet, you can create one first. If you already have one or have cloned the source code, you can skip this step.
This example is about a Timeline block component with the following specific requirements:
First, we create packages/plugins/@nocobase-sample/plugin-initializer-block-data-modal/src/client/component/Timeline.tsx file with the following content:
import React, { FC } from 'react';import { Timeline as AntdTimeline, TimelineProps as AntdTimelineProps, Spin } from 'antd';import { withDynamicSchemaProps } from "@nocobase/client";import { BlockName } from '../constants';export interface TimelineProps { data?: AntdTimelineProps['items']; loading?: boolean;}export const Timeline: FC<TimelineProps> = withDynamicSchemaProps((props) => { const { data, loading } = props; if (loading) return <div style={{ height: 100, textAlign: 'center' }}><Spin /></div> return <AntdTimeline mode='left' items={data}></AntdTimeline>}, { displayName: BlockName });
The Timeline component is essentially a component wrapped by withDynamicSchemaProps, which accepts 2 parameters:
loading: Data loading state
data: items property of Timeline component
withDynamicSchemaProps is a higher-order component used to handle dynamic properties in Schema.
Temporary page verification: We can temporarily create a page and render the Timeline component to check if it meets the requirements
Documentation example verification: You can start the documentation yarn doc plugins/@nocobase-sample/plugin-initializer-block-data-modal, and verify if it meets the requirements by writing documentation examples (TODO)
We use temporary page verification as an example. We create a new page and add one or more Timeline components according to property parameters to check if they meet the requirements.
According to the requirements, we need to configure Time Field and Title Field after selecting the data table, so we need to define a configuration form, named TimelineInitializerConfigForm.
We defined a createSchema function to generate the configuration form Schema, which accepts a fields parameter, which is the fields of the data table.
The above effect is that there is a form inside the modal, and there are 2 selectors in the form, one is Title Field, one is Time Field, and there are a Close and Submit button.
NocoBase's dynamic pages are all rendered through Schema, so we need to define a Schema, which will be used later to add the Timeline block to the interface. Before implementing this section, we need to understand some basic knowledge:
UI Schema Protocol: Detailed introduction to the structure of Schema and the role of each property
getTimelineSchema() accepts dataSource, collection, titleField, timeField and returns a Schema, which is used to render the Timeline block:
type: 'void': Indicates no data
x-decorator: 'DataBlockProvider': Data block provider, used to provide data. For more information about DataBlockProvider, please refer to DataBlockProvider
x-decorator-props: Properties of DataBlockProvider
dataSource: Data source
collection: Data table
action: 'list': Operation type, here it is list, to get the data list
params: { sort }: Request parameters, here we sort timeField in descending order. For more information about request parameters, please refer to useRequest
x-component: 'CardItem': CardItem component, currently all blocks are wrapped in cards, which provide styles, layouts, and drag-and-drop functionality
'x-component': 'Timeline': Block component, which is the Timeline component we defined
'x-use-component-props': 'useTimelineProps': Used to handle the dynamic properties of the Timeline component, and because it needs to be stored in the database, the value type here is a string type.
useTimelineProps(): Dynamic properties of the Timeline component
We modify packages/plugins/@nocobase-sample/plugin-initializer-block-data-modal/src/client/index.tsx file to register useTimelineProps to the system, so that x-use-component-props can find the corresponding scope.
import { Plugin } from '@nocobase/client';import { Timeline } from './component';import { useTimelineProps } from './schema';export class PluginInitializerBlockDataModalClient extends Plugin { async load() { this.app.addComponents({ Timeline }) this.app.addScopes({ useTimelineProps }); }}export default PluginInitializerBlockDataModalClient;
Same as verifying components, we can verify the Schema by temporary page verification or documentation example verification. Here we use temporary page verification as an example:
We create packages/plugins/@nocobase-sample/plugin-initializer-block-data-modal/src/client/initializer/index.tsx file to define Schema Initializer Item:
The operation flow is to first click on the data table to get the values of collection and dataSource, then get the timeField and titleField fields through the configuration form TimelineInitializerConfigForm, and when the form is submitted, create a schema based on the data and insert it into the page.
The core to achieving the data block effect is DataBlockInitializer (documentation TODO).
A complete Block also needs to have Schema Settings, which are used to configure some properties and operations, but Schema Settings is not the focus of this example, so we only have a remove operation here.
We create packages/plugins/@nocobase-sample/plugin-initializer-block-data-modal/src/client/settings/index.ts file with the following content:
removeParentsIfNoChildren: Whether to delete the parent node if there are no child nodes
breakRemoveOn: Break condition when deleting. Because Add Block automatically wraps children in Grid, we set breakRemoveOn: { 'x-component': 'Grid' } here, so when deleting Grid, it doesn't delete upwards anymore.
If we need to add it to the page-level Add block, we need to know the corresponding name. We can view the corresponding name through TODO method.
TODO
From the above figure, we can see that the page-level Add block corresponds to the name page:addBlock, and Data Blocks corresponds to the name dataBlocks.
Then we modify packages/plugins/@nocobase-sample/plugin-initializer-block-data-modal/src/client/index.tsx file:
import { Plugin } from '@nocobase/client';import { Timeline } from './component';import { useTimelineProps } from './schema';import { timelineSettings } from './settings';import { timelineInitializerItem } from './timelineInitializerItem';export class PluginInitializerBlockDataModalClient extends Plugin { async load() { this.app.addComponents({ Timeline }) this.app.addScopes({ useTimelineProps }); this.app.schemaSettingsManager.add(timelineSettings) this.app.schemaInitializerManager.addItem('page:addBlock', `dataBlocks.${timelineInitializerItem.name}`, timelineInitializerItem) }}export default PluginInitializerBlockDataModalClient;
We need to add it not only to the page-level Add block, but also to the Add block in the Table block Add new modal.
According to the method of obtaining the page-level name, we get the Add block name of the Table block as popup:addNew:addBlock, and Data Blocks corresponds to the name dataBlocks.
Then modify packages/plugins/@nocobase-sample/plugin-initializer-block-data-modal/src/client/index.tsx file:
import { Plugin } from '@nocobase/client';import { Timeline } from './component';import { useTimelineProps } from './schema';import { timelineSettings } from './settings';import { timelineInitializerItem } from './timelineInitializerItem';export class PluginInitializerBlockDataModalClient extends Plugin { async load() { this.app.addComponents({ Timeline }) this.app.addScopes({ useTimelineProps }); this.app.schemaSettingsManager.add(timelineSettings) this.app.schemaInitializerManager.addItem('page:addBlock', `dataBlocks.${timelineInitializerItem.name}`, timelineInitializerItem)+ this.app.schemaInitializerManager.addItem('popup:addNew:addBlock', `dataBlocks.${timelineInitializerItem.name}`, timelineInitializerItem); }}export default PluginInitializerBlockDataModalClient;