Skip to content

Commit f9dd21f

Browse files
eneufeldedgarmueller
authored andcommitted
Add simple any of renderer
* this renderer handles only cases where the schema has an anyof with strings and enums.
1 parent 9a0d704 commit f9dd21f

File tree

5 files changed

+310
-6
lines changed

5 files changed

+310
-6
lines changed

packages/examples/src/anyOf.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,28 @@ const data = {
9292
}
9393
};
9494

95+
const schema_simple = {
96+
type: 'object',
97+
properties: {
98+
foo: {
99+
anyOf: [{ type: 'string' }, { enum: ['foo', 'bar'] }]
100+
}
101+
}
102+
};
103+
95104
registerExamples([
96105
{
97106
name: 'anyOf',
98107
label: 'anyOf',
99108
data,
100109
schema,
101110
uischema
111+
},
112+
{
113+
name: 'anyOf_simple',
114+
label: 'AnyOf Simple',
115+
data: { foo: 'foo' },
116+
schema: schema_simple,
117+
uischema: undefined
102118
}
103119
]);
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
The MIT License
3+
4+
Copyright (c) 2018-2019 EclipseSource Munich
5+
https://github.com/eclipsesource/jsonforms
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in
15+
all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
THE SOFTWARE.
24+
*/
25+
import {
26+
and,
27+
ControlProps,
28+
ControlState,
29+
EnumCellProps,
30+
JsonSchema,
31+
mapDispatchToControlProps,
32+
mapStateToControlProps,
33+
RankedTester,
34+
rankWith,
35+
schemaMatches,
36+
uiTypeIs,
37+
WithClassname
38+
} from '@jsonforms/core';
39+
import { Control } from '@jsonforms/react';
40+
import { Input } from '@material-ui/core';
41+
import { InputBaseComponentProps } from '@material-ui/core/InputBase';
42+
import merge from 'lodash/merge';
43+
import React from 'react';
44+
import { connect } from 'react-redux';
45+
import { MaterialInputControl } from './MaterialInputControl';
46+
47+
const MuiAutocompleteInputText = (props: EnumCellProps & WithClassname) => {
48+
const {
49+
data,
50+
config,
51+
className,
52+
id,
53+
enabled,
54+
uischema,
55+
isValid,
56+
path,
57+
handleChange,
58+
schema
59+
} = props;
60+
61+
const findEnumSchema = (schemas: JsonSchema[]) =>
62+
schemas.find(
63+
s => s.enum !== undefined && (s.type === 'string' || s.type === undefined)
64+
);
65+
const findTextSchema = (schemas: JsonSchema[]) =>
66+
schemas.find(s => s.type === 'string' && s.enum === undefined);
67+
const enumSchema = findEnumSchema(schema.anyOf);
68+
const textSchema = findTextSchema(schema.anyOf);
69+
const maxLength = textSchema.maxLength;
70+
const mergedConfig = merge({}, config, uischema.options);
71+
let inputProps: InputBaseComponentProps = {};
72+
if (mergedConfig.restrict) {
73+
inputProps = { maxLength: maxLength };
74+
}
75+
if (mergedConfig.trim && maxLength !== undefined) {
76+
inputProps.size = maxLength;
77+
}
78+
const onChange = (ev: any) => handleChange(path, ev.target.value);
79+
80+
inputProps.list = props.id + 'datalist';
81+
const dataList = (
82+
<datalist id={props.id + 'datalist'}>
83+
{enumSchema.enum.map(optionValue => (
84+
<option value={optionValue} key={optionValue} />
85+
))}
86+
</datalist>
87+
);
88+
return (
89+
<Input
90+
type='text'
91+
value={data || ''}
92+
onChange={onChange}
93+
className={className}
94+
id={id}
95+
disabled={!enabled}
96+
autoFocus={uischema.options && uischema.options.focus}
97+
fullWidth={!mergedConfig.trim || maxLength === undefined}
98+
inputProps={inputProps}
99+
error={!isValid}
100+
endAdornment={dataList}
101+
/>
102+
);
103+
};
104+
105+
export class MaterialSimpleAnyOfControl extends Control<ControlProps, ControlState> {
106+
render() {
107+
return (
108+
<MaterialInputControl {...this.props} input={MuiAutocompleteInputText} />
109+
);
110+
}
111+
}
112+
const hasEnumAndText = (schemas: JsonSchema[]) => {
113+
// idea: map to type,enum and check that all types are string and at least one item is of type enum,
114+
const enumSchema = schemas.find(
115+
s => s.enum !== undefined && (s.type === 'string' || s.type === undefined)
116+
);
117+
const textSchema = schemas.find(
118+
s => s.type === 'string' && s.enum === undefined
119+
);
120+
const remainingSchemas = schemas.filter(
121+
s => s !== enumSchema || s !== textSchema
122+
);
123+
const wrongType = remainingSchemas.find(s => s.type && s.type !== 'string');
124+
return enumSchema && textSchema && !wrongType;
125+
};
126+
const simpleAnyOf = and(
127+
uiTypeIs('Control'),
128+
schemaMatches(
129+
schema => schema.hasOwnProperty('anyOf') && hasEnumAndText(schema.anyOf)
130+
)
131+
);
132+
export const materialSimpleAnyOfControlTester: RankedTester = rankWith(
133+
5,
134+
simpleAnyOf
135+
);
136+
export default connect(
137+
mapStateToControlProps,
138+
mapDispatchToControlProps
139+
)(MaterialSimpleAnyOfControl);

packages/material/src/controls/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ import MaterialTextControl, {
5353
materialTextControlTester
5454
} from './MaterialTextControl';
5555

56+
import MaterialSimpleAnyOfControl, {
57+
materialSimpleAnyOfControlTester
58+
} from './MaterialSimpleAnyOfControl';
59+
5660
export {
5761
MaterialBooleanControl,
5862
materialBooleanControlTester,
@@ -73,5 +77,7 @@ export {
7377
MaterialNumberControl,
7478
materialNumberControlTester,
7579
MaterialTextControl,
76-
materialTextControlTester
80+
materialTextControlTester,
81+
MaterialSimpleAnyOfControl,
82+
materialSimpleAnyOfControlTester
7783
};

packages/material/src/index.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,18 @@ import {
5555
materialEnumControlTester,
5656
MaterialIntegerControl,
5757
materialIntegerControlTester,
58-
MaterialNumberControl,
59-
materialNumberControlTester,
60-
MaterialTextControl,
61-
materialTextControlTester,
6258
MaterialNativeControl,
6359
materialNativeControlTester,
60+
MaterialNumberControl,
61+
materialNumberControlTester,
6462
MaterialRadioGroupControl,
6563
materialRadioGroupControlTester,
64+
MaterialSimpleAnyOfControl,
65+
materialSimpleAnyOfControlTester,
6666
MaterialSliderControl,
67-
materialSliderControlTester
67+
materialSliderControlTester,
68+
MaterialTextControl,
69+
materialTextControlTester
6870
} from './controls';
6971
import {
7072
MaterialArrayLayout,
@@ -151,6 +153,10 @@ export const materialRenderers: JsonFormsRendererRegistryEntry[] = [
151153
{
152154
tester: materialListWithDetailTester,
153155
renderer: MaterialListWithDetailRenderer
156+
},
157+
{
158+
tester: materialSimpleAnyOfControlTester,
159+
renderer: MaterialSimpleAnyOfControl
154160
}
155161
];
156162

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
The MIT License
3+
4+
Copyright (c) 2017-2019 EclipseSource Munich
5+
https://github.com/eclipsesource/jsonforms
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in
15+
all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
THE SOFTWARE.
24+
*/
25+
import React from 'react';
26+
import { Provider } from 'react-redux';
27+
28+
import Enzyme, { mount, ReactWrapper } from 'enzyme';
29+
import Adapter from 'enzyme-adapter-react-16';
30+
import {
31+
Actions,
32+
ControlElement,
33+
jsonformsReducer,
34+
JsonFormsState,
35+
JsonSchema,
36+
UISchemaElement
37+
} from '@jsonforms/core';
38+
import {
39+
materialRenderers, MaterialSimpleAnyOfControl, materialSimpleAnyOfControlTester
40+
} from '../../src';
41+
import { combineReducers, createStore, Store } from 'redux';
42+
43+
Enzyme.configure({ adapter: new Adapter() });
44+
45+
const schema: JsonSchema = {
46+
anyOf: [{ type: 'string' }, { enum: ['foo', 'bar'] }]
47+
};
48+
49+
const uischema: ControlElement = {
50+
type: 'Control',
51+
scope: '#'
52+
};
53+
54+
describe('Material simple any of control tester', () => {
55+
it('should only be applicable for simple any of cases', () => {
56+
expect(materialSimpleAnyOfControlTester({ type: 'Foo' }, schema)).toBe(-1);
57+
expect(materialSimpleAnyOfControlTester(uischema, schema)).toBe(5);
58+
59+
const nestedSchema: JsonSchema = {
60+
properties: {
61+
foo: { anyOf: [{ type: 'string' }, { enum: ['foo', 'bar'] }] }
62+
}
63+
};
64+
const nestedUischema: ControlElement = {
65+
type: 'Control',
66+
scope: '#/properties/foo'
67+
};
68+
expect(materialSimpleAnyOfControlTester(nestedUischema, nestedSchema)).toBe(
69+
5
70+
);
71+
const schemaNoEnum: JsonSchema = {
72+
anyOf: [{ type: 'string' }]
73+
};
74+
const schemaConflictTypes: JsonSchema = {
75+
anyOf: [{ type: 'string' }, { type: 'integer', enum: [1, 2] }]
76+
};
77+
const schemaAdditionalProps: JsonSchema = {
78+
anyOf: [{ type: 'string' }, { enum: ['foo', 'bar'] }, { maxLength: 5 }]
79+
};
80+
const schemaNoString: JsonSchema = {
81+
anyOf: [{ type: 'integer' }, { enum: [1, 2] }]
82+
};
83+
expect(materialSimpleAnyOfControlTester(uischema, schemaNoEnum)).toBe(-1);
84+
expect(
85+
materialSimpleAnyOfControlTester(uischema, schemaConflictTypes)
86+
).toBe(-1);
87+
expect(
88+
materialSimpleAnyOfControlTester(uischema, schemaAdditionalProps)
89+
).toBe(5);
90+
expect(materialSimpleAnyOfControlTester(uischema, schemaNoString)).toBe(-1);
91+
});
92+
});
93+
94+
const initJsonFormsStore = (
95+
testData: any,
96+
testSchema: JsonSchema,
97+
testUiSchema: UISchemaElement
98+
): Store<JsonFormsState> => {
99+
const s: JsonFormsState = {
100+
jsonforms: {
101+
renderers: materialRenderers
102+
}
103+
};
104+
const store: Store<JsonFormsState> = createStore(
105+
combineReducers({ jsonforms: jsonformsReducer() }),
106+
s
107+
);
108+
store.dispatch(Actions.init(testData, testSchema, testUiSchema));
109+
return store;
110+
};
111+
112+
describe('Material input control', () => {
113+
let wrapper: ReactWrapper;
114+
115+
afterEach(() => {
116+
wrapper.unmount();
117+
});
118+
119+
it('render', () => {
120+
const store = initJsonFormsStore('foo', schema, uischema);
121+
wrapper = mount(
122+
<Provider store={store}>
123+
<MaterialSimpleAnyOfControl schema={schema} uischema={uischema} />
124+
</Provider>
125+
);
126+
const inputs = wrapper.find('input');
127+
expect(inputs).toHaveLength(1);
128+
129+
const datalist = wrapper.find('datalist');
130+
expect(datalist).toHaveLength(1);
131+
expect(datalist.children()).toHaveLength(2);
132+
133+
const validation = wrapper.find('p').first();
134+
expect(validation.props().className).toContain('MuiFormHelperText-root');
135+
expect(validation.children()).toHaveLength(0);
136+
});
137+
});

0 commit comments

Comments
 (0)