Extend Your Own UI Component Type Elements
When the existing UI components in the JitAi development framework don't meet your specific business requirements, you can extend new components under components.Meta and integrate them into pages using the JitAi visual development tool.
In this guide, we'll walk through creating a simple counter component that demonstrates how to extend UI components. This counter features basic functionality including value display and increment/decrement buttons.
While JitAi's existing portal and page types are comprehensive and rarely need extension, UI components offer significant opportunities for customization to meet diverse presentation requirements.
Effect preview
Element design
| Element Level | fullName | Main Responsibilities |
|---|---|---|
| Meta Element | components.Meta | Existing Meta element in JitAi development framework (no need to create) |
| Type Element | components.CounterType | type points to components.Meta, encapsulates basic counter functionality, handles value increment/decrement, style configuration, etc. |
| Configuration when used in pages | Page scheme.json | Configure specific runtime parameters for components in pages and declare events, functions, variables |
Counter component directory structure
├── components/
│ └── CounterType/
│ ├── e.json
│ ├── constants.ts # Constant definitions
│ ├── index.ts # PC entry file
│ ├── index.mobile.ts # Mobile entry file
│ ├── CounterComponent.ts # Business logic layer (shared by PC and mobile)
│ └── render/
│ ├── pc/
│ │ └── CounterRender.tsx # PC rendering component
│ └── mobile/
│ └── CounterRender.tsx # Mobile rendering component
Operation guide
Creating directory structure
In your JitAi application root directory, create directories according to the following structure:
# Execute in application root directory
mkdir -p components/CounterType
mkdir -p components/CounterType/render/pc
mkdir -p components/CounterType/render/mobile
Implementing element files
- Element Definition File
- Entry Files
- Render Components
- Business Logic
Create element declaration file components/CounterType/e.json:
{
"frontBundleEntry": "./index.ts",
"frontMobileBundleEntry": "./index.mobile.ts",
"description": "Counter Type element, encapsulates core counter functionality",
"title": "Counter Component",
"type": "components.Meta",
"outputName": "index"
}
Create PC entry file components/CounterType/index.ts:
import Render from './render/pc/CounterRender';
import { CounterComponent } from './CounterComponent';
export { COUNTER_EVENTS } from './constants';
// Rename exports for JitAi tool recognition
export { CounterComponent as ComponentCls, Render };
Create mobile entry file components/CounterType/index.mobile.ts:
import Render from './render/mobile/CounterRender';
import { CounterComponent } from './CounterComponent';
export { COUNTER_EVENTS } from './constants';
// Rename exports for JitAi tool recognition
export { CounterComponent as ComponentCls, Render };
Create PC rendering component components/CounterType/render/pc/CounterRender.tsx:
import React, { useState } from 'react';
import { Button, Typography, Space } from 'antd';
const { Title, Text } = Typography;
interface CounterRenderProps {
compIns: any; // Component instance
}
const CounterRender: React.FC<CounterRenderProps> = ({ compIns }) => {
const [count, setCount] = useState(compIns.config?.initialValue || 0);
const handleIncrement = () => {
const newValue = count + 1;
setCount(newValue);
compIns.value = newValue;
};
const handleDecrement = () => {
const newValue = count - 1;
setCount(newValue);
compIns.value = newValue;
};
return (
<div>
<Title level={4}>Counter</Title>
<Space>
<Button onClick={handleDecrement}>-</Button>
<Text>{count}</Text>
<Button onClick={handleIncrement}>+</Button>
</Space>
</div>
);
};
export default CounterRender;
Create mobile rendering component components/CounterType/render/mobile/CounterRender.tsx:
import React, { useState } from 'react';
import { Button, Typography, Space } from 'antd';
const { Title, Text } = Typography;
interface CounterRenderProps {
compIns: any; // Component instance
}
const CounterRender: React.FC<CounterRenderProps> = ({ compIns }) => {
const [count, setCount] = useState(compIns.config?.initialValue || 0);
const handleIncrement = () => {
const newValue = count + 1;
setCount(newValue);
compIns.value = newValue;
};
const handleDecrement = () => {
const newValue = count - 1;
setCount(newValue);
compIns.value = newValue;
};
return (
<div>
<Title level={5}>Counter</Title>
<Space>
<Button onClick={handleDecrement}>-</Button>
<Text>{count}</Text>
<Button onClick={handleIncrement}>+</Button>
</Space>
</div>
);
};
export default CounterRender;
Create constants file components/CounterType/constants.ts:
// Counter event type constants
export const COUNTER_EVENTS = {
VALUE_CHANGE: 'valueChange'
} as const;
Create component logic class components/CounterType/CounterComponent.ts:
import type { ComponentConfig } from 'components/Meta/frontend/type';
import { Jit } from 'jit';
export class CounterComponent extends Jit.BaseComponent {
/**
* Must implement: Get component variable list
* Used by visual editor to display available variables
After the variable is declared here, Jit will automatically generate variables based on the name: this.value = {
value: 0,
title: 'Current Value',
}
*/
static getVariableList(compConfig: Record<string, any>) {
return [
{
name: 'value',
title: 'Current Value',
dataType: 'Numeric',
value:0
}
];
}
/**
* Must implement: Get component function list
* Used by visual editor to display callable methods
*/
static getFuncList(compConfig: Record<string, any>) {
return [
{
title: 'Get Value',
name: 'getValue',
args: []
}
];
}
/**
* Must implement: Get component event list
* Used by visual editor to display listenable events
*/
static getEventList() {
return [
{
name: 'valueChange',
title: 'Value Change',
data: 'eventData
}
];
}
constructor(componentInfo: ComponentConfig<any>) {
super(componentInfo);
this.value.value = this.config?.initialValue || 0;
}
getValue(): number {
return this.value.value;
}
async init() {
await super.init();
}
}
To ensure your component is properly recognized and loaded by the page editor, follow these requirements:
1. Use exact export names
- Component class must be exported as
ComponentCls - Renderer class must be exported as
Render - Internal class names can be customized (e.g.,
CounterComponent,CounterRender)
2. Implement three static methods
// Get component variable list (used by editor to display available variables)
static getVariableList(compConfig: Record<string, any>): Array<{
name: string;
title: string;
dataType: 'Numeric' | 'JSON' | 'Text';
readonly?: boolean;
}>
// Get component function list (used by editor to display callable methods)
static getFuncList(compConfig: Record<string, any>): Array<{
title: string;
name: string;
args: Array<{
name: string;
title: string;
dataType: 'Numeric' | 'JSON' | 'Text';
}>;
}>
// Get component event list (used by editor to display listenable events)
static getEventList(): Array<{
name: string;
title: string;
data: string;
}>
Method descriptions:
getVariableList: Declares component variables available for reference in pagesgetFuncList: Declares component methods available for invocation in pagesgetEventList: Declares events that the component will emit for pages to listen to
Testing
Making new elements take effect
- Clear cache: Delete the
distdirectory in the application directory - Restart service: Restart the desktop client
- Trigger packaging: Access the application page, the system will automatically repackage
Testing in generic pages
Enter the JitAi visual development tool, create a Generic Page, and add the newly created Counter component to the page.
When switching to code mode, you'll see the following configuration automatically generated in the page's scheme.json file:
{
"layout": [
{
"i": "CounterType2",
"x": 0,
"y": 0,
"w": 48,
"h": 30
}
],
"componentList": [
{
"fullName": "components.CounterType",
"type": "components.CounterType",
"name": "CounterType2",
"title": "Counter Component 2",
"config": {
"requireElements": []
},
"showTitle": true,
"eventList": [
{
"name": "valueChange",
"title": "Value Change",
"data": "eventData"
}
],
"functionList": [
{
"title": "Get Value",
"name": "getValue",
"args": []
}
],
"variableList": [
{
"name": "value",
"title": "Current Value",
"dataType": "Numeric"
}
]
}
],
"autoIncrementId": 3,
"variableList": [],
"functionList": [],
"matchUarParamsVariableNameList": []
}
Verify basic functionality:
- Confirm the counter component renders correctly on the page
- Click the plus button to verify value increments
- Click the minus button to verify value decrements
- Check that the component title displays properly
Try modifying configuration items in the scheme.json file (such as name, title, or showTitle), then switch to visual mode to observe the changes in the counter component.
Summary
Core steps for extending UI component Type elements:
- Create directory structure:
components/YourType/+ required files - Configure e.json: Set
type: "components.Meta" - Implement component class: Extend
BaseComponentand implement three static methods - Create render component: Build a React component that receives the
compInsparameter - Export correctly: Use
ComponentClsandRenderas export names
Key points:
- Export names
ComponentClsandRendermust not be changed - The three static methods
getVariableList,getFuncList, andgetEventListmust be implemented
Advanced thinking
While manually modifying configuration items in the page's scheme.json file is feasible, it's not intuitive or user-friendly, making it inconvenient for business users during visual development.
How can we configure counter component parameters through the visual interface, just like the official components?
Please refer to Developing Visual Editors for UI Component Type Elements.