docs/docs/en/plugin-development/client/examples/custom-block.md
In NocoBase, blocks are content areas on a page. This example demonstrates how to build the simplest custom block using BlockModel -- supporting HTML content editing through the UI, allowing users to modify the block's displayed content via a configuration panel.
This is the first example involving FlowEngine, using BlockModel, renderComponent, registerFlow, and uiSchema.
:::tip Prerequisites
It's recommended to familiarize yourself with the following content for a smoother development experience:
load() lifecycletExpr() deferred translation usage:::
We're building a "Simple block":
Full source code is available at @nocobase-example/plugin-simple-block. If you want to run it locally:
yarn pm enable @nocobase-example/plugin-simple-block
Let's build this plugin step by step from scratch.
Run the following in the repository root:
yarn pm create @my-project/plugin-simple-block
This will generate a basic file structure under packages/plugins/@my-project/plugin-simple-block. For detailed instructions, see Writing Your First Plugin.
Create src/client-v2/models/SimpleBlockModel.tsx. This is the core of the entire plugin -- defining how the block renders and how it's configured.
// src/client-v2/models/SimpleBlockModel.tsx
import React from 'react';
import { BlockModel } from '@nocobase/client-v2';
import { tExpr } from '../locale';
export class SimpleBlockModel extends BlockModel {
renderComponent() {
return <div dangerouslySetInnerHTML={{ __html: this.props.html }} />;
}
}
// Set the display name in the "Add Block" menu
SimpleBlockModel.define({
label: tExpr('Simple block'),
});
// Register a configuration panel to let users edit HTML content
SimpleBlockModel.registerFlow({
key: 'flow1',
title: tExpr('Simple Block Flow'),
on: 'beforeRender', // Execute before rendering
steps: {
editHtml: {
title: tExpr('Edit HTML Content'),
// uiSchema defines the form UI of the configuration panel
uiSchema: {
html: {
type: 'string',
title: tExpr('HTML Content'),
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
},
},
// Default values for the configuration panel
defaultParams: {
html: `<h3>This is a simple block</h3>
<p>You can edit the HTML content.</p>`,
},
// Write configuration panel values to model's props
handler(ctx, params) {
ctx.model.props.html = params.html;
},
},
},
});
Key points:
renderComponent() -- Renders the block UI, reading HTML content via this.props.htmldefine() -- Sets the display name in the "Add Block" menu. tExpr() is used for deferred translation because define() executes at module load time, when i18n hasn't been initialized yetregisterFlow() -- Adds a configuration panel. uiSchema defines the form using JSON Schema (syntax reference: UI Schema), handler writes user-filled values to ctx.model.props, and renderComponent() can read themEdit the translation files under the plugin's src/locale/, adding translations for all keys used by tExpr():
// src/locale/zh-CN.json
{
"Simple block": "简单区块",
"Simple Block Flow": "简单区块配置",
"Edit HTML Content": "编辑 HTML 内容",
"HTML Content": "HTML 内容"
}
// src/locale/en-US.json
{
"Simple block": "Simple block",
"Simple Block Flow": "Simple Block Flow",
"Edit HTML Content": "Edit HTML Content",
"HTML Content": "HTML Content"
}
:::warning Note
Adding language files for the first time requires restarting the application to take effect.
:::
For more about translation file conventions and tExpr() usage, see i18n Internationalization.
Edit src/client-v2/plugin.tsx to lazy-load the model via registerModelLoaders:
// src/client-v2/plugin.tsx
import { Plugin } from '@nocobase/client-v2';
export class PluginSimpleBlockClient extends Plugin {
async load() {
this.flowEngine.registerModelLoaders({
SimpleBlockModel: {
// Lazy loading -- the module is only loaded when first used
loader: () => import('./models/SimpleBlockModel'),
},
});
}
}
export default PluginSimpleBlockClient;
registerModelLoaders uses dynamic imports so that model code is only loaded when actually needed -- this is the recommended registration approach.
yarn pm enable @my-project/plugin-simple-block
After enabling, create a new page and click "Add Block" to see "Simple block". Once added, click the block's configuration button to edit the HTML content.
Capabilities used in this example:
| Capability | Usage | Documentation |
|---|---|---|
| Block Rendering | BlockModel + renderComponent() | FlowEngine -> Block Extension |
| Menu Registration | define({ label }) | FlowEngine Overview |
| Configuration Panel | registerFlow() + uiSchema | FlowEngine Overview |
| Model Registration | registerModelLoaders (lazy loading) | Plugin |
| Deferred Translation | tExpr() | i18n Internationalization |