Skip to content

Commit 9fba601

Browse files
committed
[material] Fix binding of object schema in tables
1 parent 5c6e488 commit 9fba601

File tree

4 files changed

+178
-56
lines changed

4 files changed

+178
-56
lines changed

packages/examples/src/1253.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,41 @@ const uischema3 = {
102102
scope: '#/properties/thingOrThings'
103103
};
104104

105+
const schema4 = {
106+
definitions: {
107+
color: {
108+
type: 'string',
109+
enum: ['red', 'green', 'blue']
110+
}
111+
},
112+
type: 'object',
113+
properties: {
114+
things: {
115+
type: 'array',
116+
items: {
117+
type: 'object',
118+
properties: {
119+
somethingElse: {
120+
type: 'string'
121+
},
122+
thing: {
123+
type: 'string',
124+
enum: ['thing']
125+
},
126+
anotherThing: {
127+
$ref: '#/definitions/color'
128+
}
129+
}
130+
}
131+
}
132+
}
133+
};
134+
135+
const uischema4 = {
136+
type: 'Control',
137+
scope: '#/properties/things'
138+
};
139+
105140
registerExamples([
106141
{
107142
name: 'issue-1253',
@@ -141,3 +176,13 @@ registerExamples([
141176
uischema: uischema3
142177
}
143178
]);
179+
180+
registerExamples([
181+
{
182+
name: 'issue-1253-enum-error',
183+
label: 'issue 1253 (enum)',
184+
data,
185+
schema: schema4,
186+
uischema: uischema4
187+
}
188+
]);

packages/material/src/complex/MaterialTableControl.tsx

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ import {
4343
Generate,
4444
Helpers,
4545
JsonSchema,
46-
Paths
46+
Paths,
47+
Resolve
4748
} from '@jsonforms/core';
4849
import IconButton from '@material-ui/core/IconButton';
4950
import DeleteIcon from '@material-ui/icons/Delete';
@@ -65,20 +66,21 @@ const styles = {
6566

6667
const generateCells = (
6768
Cell: React.ComponentType<NonEmptyCellProps | TableHeaderCellProps>,
68-
scopedSchema: JsonSchema,
69+
rootSchema: JsonSchema,
70+
schema: JsonSchema,
6971
rowPath: string,
7072
cellErrors?: any[]
7173
) => {
7274

73-
if (scopedSchema.type === 'object') {
74-
return getValidColumnProps(scopedSchema).map(prop => {
75+
if (schema.type === 'object') {
76+
return getValidColumnProps(schema).map(prop => {
7577
const cellPath = Paths.compose(
7678
rowPath,
7779
prop
7880
);
7981
const props = {
8082
propName: prop,
81-
scopedSchema,
83+
schema,
8284
rowPath,
8385
cellPath,
8486
errors: cellErrors
@@ -89,7 +91,8 @@ const generateCells = (
8991
} else {
9092
// primitives
9193
const props = {
92-
scopedSchema,
94+
schema,
95+
rootSchema,
9396
rowPath,
9497
cellPath: rowPath,
9598
errors: cellErrors
@@ -131,33 +134,47 @@ const TableHeaderCell = ({ propName }: TableHeaderCellProps) => (
131134
interface NonEmptyCellProps {
132135
rowPath: string;
133136
propName?: string;
134-
scopedSchema: JsonSchema;
137+
schema: JsonSchema;
138+
rootSchema: JsonSchema;
135139
errors?: any[];
136140
}
137141

138142
const NonEmptyCell = ({
139143
rowPath,
140144
propName,
141-
scopedSchema,
145+
schema,
146+
rootSchema,
142147
errors
143148
}: NonEmptyCellProps) => {
144-
const path = rowPath + (scopedSchema.type === 'object' ? '.' + propName : '');
149+
const path = rowPath + (schema.type === 'object' ? '.' + propName : '');
145150
const errorsPerEntry: any[] = filter(
146151
errors,
147152
error => error.dataPath === path
148153
).map(e => e.message);
149154
const isValid = isEmpty(errorsPerEntry);
155+
150156
return (
151157
<React.Fragment>
152158
<NoBorderTableCell>
153-
<DispatchField
154-
schema={scopedSchema}
155-
uischema={Generate.controlElement(
156-
undefined,
157-
scopedSchema.type === 'object' ? `#/properties/${propName}` : '#'
158-
)}
159-
path={path}
160-
/>
159+
{
160+
schema.properties ?
161+
<DispatchField
162+
schema={Resolve.schema(schema, `#/properties/${propName}`, rootSchema)}
163+
uischema={Generate.controlElement(
164+
undefined,
165+
`#/properties/${propName}`
166+
)}
167+
path={path}
168+
/> :
169+
<DispatchField
170+
schema={schema}
171+
uischema={Generate.controlElement(
172+
undefined,
173+
'#'
174+
)}
175+
path={path}
176+
/>
177+
}
161178
<FormHelperText error={!isValid}>
162179
{!isValid && formatErrorMessage(errorsPerEntry)}
163180
</FormHelperText>
@@ -168,20 +185,22 @@ const NonEmptyCell = ({
168185

169186
interface NonEmptyRowProps {
170187
childPath: string;
171-
scopedSchema: JsonSchema;
188+
schema: JsonSchema;
189+
rootSchema: JsonSchema;
172190
childErrors: ErrorObject[];
173191
rowData: any;
174192
}
175193

176194
const NonEmptyRow = ({
177195
childPath,
178-
scopedSchema,
196+
schema,
197+
rootSchema,
179198
childErrors,
180199
rowData,
181200
openDeleteDialog
182201
}: NonEmptyRowProps & WithDeleteDialogSupport) => (
183202
<TableRow key={childPath} hover>
184-
{generateCells(NonEmptyCell, scopedSchema, childPath, childErrors)}
203+
{generateCells(NonEmptyCell, rootSchema, schema, childPath, childErrors)}
185204
<NoBorderTableCell style={styles.fixedCell}>
186205
<div style={{ display: 'flex', justifyContent: 'center' }}>
187206
<IconButton
@@ -197,7 +216,7 @@ const NonEmptyRow = ({
197216

198217
const TableRows = (
199218
{
200-
data, path, schema, childErrors, openDeleteDialog
219+
data, path, rootSchema, schema, childErrors, openDeleteDialog
201220
}: ArrayControlProps & WithDeleteDialogSupport) => {
202221

203222
const isEmptyTable = !data || !Array.isArray(data) || data.length === 0;
@@ -214,7 +233,8 @@ const TableRows = (
214233
key={childPath}
215234
childPath={childPath}
216235
rowData={_child}
217-
scopedSchema={schema}
236+
schema={schema}
237+
rootSchema={rootSchema}
218238
childErrors={childErrors}
219239
openDeleteDialog={openDeleteDialog}
220240
/>
@@ -239,12 +259,13 @@ export class MaterialTableControl extends React.Component<
239259
addItem,
240260
openDeleteDialog
241261
} = this.props;
262+
242263
const controlElement = uischema as ControlElement;
243264
const labelObject = Helpers.createLabelDescriptionFrom(controlElement);
244265
const allErrors = [].concat(errors).concat(childErrors.map((e: ErrorObject) => e.message));
245266
const isObjectSchema = schema.type === 'object';
246267
const headerCells: any = isObjectSchema ?
247-
generateCells(TableHeaderCell, schema, path) : undefined;
268+
generateCells(TableHeaderCell, rootSchema, schema, path) : undefined;
248269

249270
return (
250271
<React.Fragment>

packages/material/test/renderers/MaterialArrayControl.test.tsx

Lines changed: 86 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -53,38 +53,38 @@ export const initJsonFormsStore = (customData?: any): Store<JsonFormsState> => {
5353
};
5454

5555
const fixture: {
56-
data: any,
57-
schema: JsonSchema,
58-
uischema: ControlElement
56+
data: any,
57+
schema: JsonSchema,
58+
uischema: ControlElement
5959
} = {
60-
data: [
61-
{
62-
message: 'El Barto was here',
63-
done: true
60+
data: [
61+
{
62+
message: 'El Barto was here',
63+
done: true
64+
},
65+
{
66+
message: 'Yolo'
67+
}
68+
],
69+
schema: {
70+
type: 'array',
71+
items: {
72+
type: 'object',
73+
properties: {
74+
message: {
75+
type: 'string',
76+
maxLength: 3
6477
},
65-
{
66-
message: 'Yolo'
67-
}
68-
],
69-
schema: {
70-
type: 'array',
71-
items: {
72-
type: 'object',
73-
properties: {
74-
message: {
75-
type: 'string',
76-
maxLength: 3
77-
},
78-
done: {
79-
type: 'boolean'
80-
}
81-
}
78+
done: {
79+
type: 'boolean'
8280
}
83-
},
84-
uischema: {
85-
type: 'Control',
86-
scope: '#'
81+
}
8782
}
83+
},
84+
uischema: {
85+
type: 'Control',
86+
scope: '#'
87+
}
8888
};
8989

9090
describe('Material array control', () => {
@@ -197,10 +197,10 @@ describe('Material array control', () => {
197197

198198
const buttons = wrapper.find('button');
199199
// 5 buttons
200-
// add row
201-
// delete row
202-
// delete row
203-
// two dialog buttons (no + yes)
200+
// add row
201+
// delete row
202+
// delete row
203+
// two dialog buttons (no + yes)
204204
const nrOfRowsBeforeDelete = wrapper.find('tr').length;
205205

206206
const deleteButton = buttons.at(1);
@@ -215,4 +215,58 @@ describe('Material array control', () => {
215215
expect(nrOfRowsAfterDelete).toBe(3);
216216
expect(store.getState().jsonforms.core.data.length).toBe(1);
217217
});
218+
219+
it('should support adding rows that contain enums', () => {
220+
const schema = {
221+
type: 'object',
222+
properties: {
223+
things: {
224+
type: 'array',
225+
items: {
226+
type: 'object',
227+
properties: {
228+
somethingElse: {
229+
type: 'string'
230+
},
231+
thing: {
232+
type: 'string',
233+
enum: [
234+
'thing'
235+
]
236+
},
237+
}
238+
}
239+
}
240+
}
241+
};
242+
const uischema: ControlElement = {
243+
type: 'Control',
244+
scope: '#/properties/things'
245+
};
246+
const store = initJsonFormsStore();
247+
store.dispatch(Actions.init({}, schema, uischema));
248+
249+
wrapper = mount(
250+
<Provider store={store}>
251+
<MaterialArrayControlRenderer schema={schema} uischema={uischema} />
252+
</Provider>
253+
);
254+
255+
const buttons = wrapper.find('button');
256+
// 3 buttons
257+
// add row
258+
// two dialog buttons (no + yes)
259+
const nrOfRowsBeforeDelete = wrapper.find('tr').length;
260+
261+
const addButton = buttons.at(0);
262+
addButton.simulate('click');
263+
addButton.simulate('click');
264+
wrapper.update();
265+
const nrOfRowsAfterDelete = wrapper.find('tr').length;
266+
267+
// 2 header rows + 'no data' row
268+
expect(nrOfRowsBeforeDelete).toBe(3);
269+
expect(nrOfRowsAfterDelete).toBe(4);
270+
expect(store.getState().jsonforms.core.data).toEqual({ things: [{}, {}]});
271+
});
218272
});

packages/vanilla/src/complex/TableArrayControl.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
mapStateToArrayControlProps,
4040
Paths,
4141
RankedTester,
42+
Resolve,
4243
StatePropsOfControl,
4344
Test
4445
} from '@jsonforms/core';
@@ -70,6 +71,7 @@ class TableArrayControl extends React.Component<
7071
addItem,
7172
uischema,
7273
schema,
74+
rootSchema,
7375
createDefaultValue,
7476
path,
7577
data,
@@ -160,7 +162,7 @@ class TableArrayControl extends React.Component<
160162
return (
161163
<td key={childPropPath}>
162164
<DispatchField
163-
schema={schema}
165+
schema={Resolve.schema(schema, `#/properties/${prop}`, rootSchema)}
164166
uischema={createControlElement(prop)}
165167
path={childPath + '.' + prop}
166168
/>

0 commit comments

Comments
 (0)