Skip to content
This repository was archived by the owner on Nov 19, 2025. It is now read-only.

Commit 722dbc5

Browse files
committed
Add support for inline editing
- allow to modify scope of controls - allow to modify label if controls - allow to modify label of groups - allow to modify label of labels
1 parent 62a7d8d commit 722dbc5

File tree

12 files changed

+277
-20
lines changed

12 files changed

+277
-20
lines changed

jsonforms-editor/src/core/api/paletteService.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,19 @@
88

99
import React from 'react';
1010

11-
import { GroupIcon, HorizontalIcon, LabelIcon, VerticalIcon } from '../icons';
11+
import {
12+
ControlIcon,
13+
GroupIcon,
14+
HorizontalIcon,
15+
LabelIcon,
16+
VerticalIcon,
17+
} from '../icons';
1218
import { EditorUISchemaElement } from '../model/uischema';
13-
import { createLabel, createLayout } from '../util/generators/uiSchema';
19+
import {
20+
createEmptyControl,
21+
createLabel,
22+
createLayout,
23+
} from '../util/generators/uiSchema';
1424

1525
export interface PaletteService {
1626
getPaletteElements(): PaletteElement[];
@@ -48,6 +58,12 @@ const paletteElements: PaletteElement[] = [
4858
icon: <LabelIcon />,
4959
uiSchemaElementProvider: () => createLabel(),
5060
},
61+
{
62+
type: 'Control',
63+
label: 'Control',
64+
icon: <ControlIcon />,
65+
uiSchemaElementProvider: () => createEmptyControl(),
66+
},
5167
];
5268

5369
export class DefaultPaletteService implements PaletteService {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* ---------------------------------------------------------------------
3+
* Copyright (c) 2020 EclipseSource Munich
4+
* Licensed under MIT
5+
* https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE
6+
* ---------------------------------------------------------------------
7+
*/
8+
9+
import { Grid, TextField } from '@material-ui/core';
10+
import MenuItem from '@material-ui/core/MenuItem';
11+
import React from 'react';
12+
13+
import { useDispatch, useSchema } from '../context';
14+
import {
15+
Actions,
16+
getChildren,
17+
getScope,
18+
isArrayElement,
19+
isObjectElement,
20+
SchemaElement,
21+
} from '../model';
22+
import { EditorControl } from '../model/uischema';
23+
24+
interface EditableControlProps {
25+
uischema: EditorControl;
26+
}
27+
export const EditableControl: React.FC<EditableControlProps> = ({
28+
uischema,
29+
}) => {
30+
const dispatch = useDispatch();
31+
const baseSchema = useSchema() as SchemaElement;
32+
const handleLabelChange = (event: React.ChangeEvent<HTMLInputElement>) => {
33+
dispatch(
34+
Actions.updateUISchemaElement(uischema.uuid, {
35+
label: event.target.value,
36+
})
37+
);
38+
};
39+
const handleScopeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
40+
dispatch(
41+
Actions.changeControlScope(
42+
uischema.uuid,
43+
(event.target.value as any).scope,
44+
(event.target.value as any).uuid
45+
)
46+
);
47+
};
48+
const scopes = getChildren(baseSchema)
49+
.flatMap((child) => {
50+
// if the child is the only item of an array, use its children instead
51+
if (
52+
(isObjectElement(child) &&
53+
isArrayElement(child.parent) &&
54+
child.parent.items === child) ||
55+
isObjectElement(child)
56+
) {
57+
return getChildren(child);
58+
}
59+
return [child];
60+
})
61+
.map((e) => ({ scope: `#${getScope(e)}`, uuid: e.uuid }));
62+
const scopeValues = scopes.filter((s) => s.scope.endsWith(uischema.scope));
63+
const scopeValue =
64+
scopeValues && scopeValues.length === 1 ? scopeValues[0] : '';
65+
return (
66+
<Grid container direction={'column'}>
67+
<Grid item xs>
68+
<TextField
69+
id='filled-name'
70+
label='Label'
71+
value={uischema.label ?? ''}
72+
onChange={handleLabelChange}
73+
fullWidth
74+
/>
75+
</Grid>
76+
<Grid item xs>
77+
<TextField
78+
id='standard-select-currency'
79+
select
80+
label='Scope'
81+
value={scopeValue}
82+
onChange={handleScopeChange}
83+
fullWidth
84+
helperText='Please select your scope'
85+
>
86+
{scopes.map((scope) => (
87+
<MenuItem key={scope.scope} value={scope as any}>
88+
{scope.scope}
89+
</MenuItem>
90+
))}
91+
</TextField>
92+
</Grid>
93+
</Grid>
94+
);
95+
};

jsonforms-editor/src/core/components/Layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export const Layout: React.FC<LayoutProps> = ({
101101
paletteTabs,
102102
children,
103103
}) => {
104-
const [open, setOpen] = React.useState(true);
104+
const [open, setOpen] = React.useState(false);
105105
const [selectedTabName, setSelectedTabName] = React.useState<string>(
106106
paletteTabs ? paletteTabs[0].name : ''
107107
);

jsonforms-editor/src/core/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export * from './ErrorDialog';
1414
export * from './OkCancelDialog';
1515
export * from './ExportDialog';
1616
export * from './ShowMoreLess';
17+
export * from './EditableControl';

jsonforms-editor/src/core/model/actions.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export type CombinedAction =
1616
| AddScopedElementToLayout
1717
| MoveUiSchemaElement
1818
| RemoveUiSchemaElement
19-
| AddDetail;
19+
| AddDetail
20+
| ChangeControlScope;
2021

2122
export type EditorAction = UiSchemaAction | CombinedAction;
2223

@@ -38,6 +39,8 @@ export const UPDATE_UISCHEMA_ELEMENT: 'jsonforms-editor/UPDATE_UISCHEMA_ELEMENT'
3839
'jsonforms-editor/UPDATE_UISCHEMA_ELEMENT';
3940
export const ADD_DETAIL: 'jsonforms-editor/ADD_DETAIL' =
4041
'jsonforms-editor/ADD_DETAIL';
42+
export const CHANGE_CONTROL_SCOPE: 'jsonforms-editor/CHANGE_CONTROL_SCOPE' =
43+
'jsonforms-editor/CHANGE_CONTROL_SCOPE';
4144

4245
export interface SetSchemaAction {
4346
type: 'jsonforms-editor/SET_SCHEMA';
@@ -89,6 +92,13 @@ export interface UpdateUiSchemaElement {
8992
changedProperties: { [key: string]: any };
9093
}
9194

95+
export interface ChangeControlScope {
96+
type: 'jsonforms-editor/CHANGE_CONTROL_SCOPE';
97+
elementUUID: string;
98+
scope: string;
99+
schemaUUID: string;
100+
}
101+
92102
export interface AddDetail {
93103
type: 'jsonforms-editor/ADD_DETAIL';
94104
uiSchemaElementId: string;
@@ -167,6 +177,12 @@ const addDetail = (
167177
detail,
168178
});
169179

180+
const changeControlScope = (
181+
elementUUID: string,
182+
scope: string,
183+
schemaUUID: string
184+
) => ({ type: CHANGE_CONTROL_SCOPE, elementUUID, scope, schemaUUID });
185+
170186
export const Actions = {
171187
setSchema,
172188
setUiSchema,
@@ -177,4 +193,5 @@ export const Actions = {
177193
removeUiSchemaElement,
178194
updateUISchemaElement,
179195
addDetail,
196+
changeControlScope,
180197
};

jsonforms-editor/src/core/model/reducer.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
ADD_DETAIL,
2121
ADD_SCOPED_ELEMENT_TO_LAYOUT,
2222
ADD_UNSCOPED_ELEMENT_TO_LAYOUT,
23+
CHANGE_CONTROL_SCOPE,
2324
CombinedAction,
2425
EditorAction,
2526
MOVE_UISCHEMA_ELEMENT,
@@ -118,6 +119,24 @@ export const combinedReducer = (state: EditorState, action: CombinedAction) => {
118119
buildSchemaTree(action.schema),
119120
buildEditorUiSchemaTree(action.uiSchema)
120121
);
122+
case CHANGE_CONTROL_SCOPE:
123+
return withCloneTrees(
124+
state.uiSchema,
125+
action.elementUUID,
126+
state.schema,
127+
action.schemaUUID,
128+
state,
129+
(newUiSchema, newSchema) => {
130+
if (!newSchema || !newUiSchema || !isEditorControl(newUiSchema))
131+
return state;
132+
linkElements(newUiSchema, newSchema);
133+
newUiSchema.scope = action.scope;
134+
return {
135+
schema: getRoot(newSchema),
136+
uiSchema: getRoot(newUiSchema as EditorUISchemaElement),
137+
};
138+
}
139+
);
121140
case ADD_SCOPED_ELEMENT_TO_LAYOUT:
122141
return withCloneTrees(
123142
state.uiSchema,
@@ -361,6 +380,7 @@ export const editorReducer = (state: EditorState, action: EditorAction) => {
361380
schema: state.schema,
362381
uiSchema: uiSchemaReducer(state.uiSchema, action),
363382
};
383+
case CHANGE_CONTROL_SCOPE:
364384
case SET_SCHEMA:
365385
case SET_UISCHEMA:
366386
case SET_SCHEMAS:

jsonforms-editor/src/core/renderers/DroppableArrayControl.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ import {
1515
JsonFormsDispatch,
1616
withJsonFormsArrayControlProps,
1717
} from '@jsonforms/react';
18-
import { makeStyles, Typography } from '@material-ui/core';
18+
import { Grid, makeStyles, Typography } from '@material-ui/core';
1919
import React, { useMemo } from 'react';
2020
import { useDrop } from 'react-dnd';
2121

22+
import { EditableControl } from '../components';
2223
import { useDispatch, useSchema } from '../context';
2324
import {
2425
canDropIntoScope,
@@ -96,22 +97,28 @@ const DroppableArrayControl: React.FC<DroppableArrayControlProps> = ({
9697
return renderers && [...renderers, DroppableElementRegistration];
9798
}, [renderers]);
9899

99-
if (!uischema.options?.detail) {
100-
return (
101-
<Typography ref={drop} className={classes.root}>
102-
Default array layout. Drag and drop an item here to customize array
103-
layout.
104-
</Typography>
105-
);
106-
}
107100
return (
108-
<JsonFormsDispatch
109-
schema={schema}
110-
uischema={uischema.options.detail}
111-
path={path}
112-
renderers={renderersToUse}
113-
cells={cells}
114-
/>
101+
<Grid container direction={'column'}>
102+
<Grid item>
103+
<EditableControl uischema={uischema} />
104+
</Grid>
105+
<Grid item>
106+
{!uischema.options?.detail ? (
107+
<Typography ref={drop} className={classes.root}>
108+
Default array layout. Drag and drop an item here to customize array
109+
layout.
110+
</Typography>
111+
) : (
112+
<JsonFormsDispatch
113+
schema={schema}
114+
uischema={uischema.options.detail}
115+
path={path}
116+
renderers={renderersToUse}
117+
cells={cells}
118+
/>
119+
)}
120+
</Grid>
121+
</Grid>
115122
);
116123
};
117124

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* ---------------------------------------------------------------------
3+
* Copyright (c) 2020 EclipseSource Munich
4+
* Licensed under MIT
5+
* https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE
6+
* ---------------------------------------------------------------------
7+
*/
8+
9+
import { ControlProps, isControl, rankWith } from '@jsonforms/core';
10+
import { withJsonFormsControlProps } from '@jsonforms/react';
11+
import React from 'react';
12+
13+
import { EditableControl } from '../components';
14+
import { EditorControl } from '../model/uischema';
15+
16+
interface DroppableControlProps extends ControlProps {
17+
uischema: EditorControl;
18+
}
19+
const DroppableControl: React.FC<DroppableControlProps> = ({ uischema }) => {
20+
return <EditableControl uischema={uischema} />;
21+
};
22+
23+
export const DroppableControlRegistration = {
24+
tester: rankWith(40, isControl), // less than DroppableElement
25+
renderer: withJsonFormsControlProps(
26+
DroppableControl as React.FC<ControlProps>
27+
),
28+
};

jsonforms-editor/src/core/renderers/DroppableGroupLayout.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ import {
1313
CardHeader,
1414
Grid,
1515
makeStyles,
16+
TextField,
1617
Typography,
1718
} from '@material-ui/core';
1819
import React from 'react';
1920

21+
import { useDispatch } from '../context';
22+
import { Actions } from '../model';
2023
import { EditorLayout } from '../model/uischema';
2124
import { DroppableLayout } from './DroppableLayout';
2225

@@ -39,6 +42,14 @@ const Group: React.FC<LayoutProps> = (props) => {
3942
const { uischema } = props;
4043
const groupLayout = uischema as GroupLayout & EditorLayout;
4144
const classes = useStyles();
45+
const dispatch = useDispatch();
46+
const handleLabelChange = (event: React.ChangeEvent<HTMLInputElement>) => {
47+
dispatch(
48+
Actions.updateUISchemaElement(groupLayout.uuid, {
49+
label: event.target.value,
50+
})
51+
);
52+
};
4253
return (
4354
<Card>
4455
<CardHeader
@@ -66,6 +77,13 @@ const Group: React.FC<LayoutProps> = (props) => {
6677
)}
6778
></CardHeader>
6879
<CardContent>
80+
<TextField
81+
id='filled-name'
82+
label='Label'
83+
value={groupLayout.label ?? ''}
84+
onChange={handleLabelChange}
85+
fullWidth
86+
/>
6987
<DroppableLayout {...props} layout={groupLayout} direction={'column'} />
7088
</CardContent>
7189
</Card>

0 commit comments

Comments
 (0)