Skip to content

Commit df3072a

Browse files
committed
feat: added select control renderer for string and array of enums and oneOf
1 parent 9d3f706 commit df3072a

File tree

5 files changed

+219
-1
lines changed

5 files changed

+219
-1
lines changed

packages/angular-material/example/app/app.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ const itemTester: UISchemaTester = (_schema, schemaPath, _path) => {
5858
selector: 'app-root',
5959
template: `
6060
<h1>Angular Material Examples</h1>
61-
Data: {{ selectedExample.data | json }}
61+
<p>Data: {{ selectedExample.data | json }}</p>
62+
<p>Schema: {{ selectedExample.schema | json }}</p>
63+
<p>UI Schema: {{ selectedExample.uischema | json }}</p>
6264
<div>
6365
Example:
6466
<select (change)="onChange($event)">
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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 {
26+
ChangeDetectionStrategy,
27+
Component,
28+
Input,
29+
OnInit,
30+
} from '@angular/core';
31+
import { MatSelectChange } from '@angular/material/select';
32+
import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular';
33+
import {
34+
Actions,
35+
composeWithUi,
36+
ControlElement,
37+
OwnPropsOfControl,
38+
RankedTester,
39+
rankWith,
40+
or,
41+
and,
42+
optionIs,
43+
isEnumControl,
44+
isOneOfEnumControl,
45+
JsonSchema,
46+
resolveSchema,
47+
uiTypeIs,
48+
schemaSubPathMatches,
49+
hasType,
50+
schemaMatches,
51+
} from '@jsonforms/core';
52+
53+
@Component({
54+
selector: 'SelectControlRenderer',
55+
template: `
56+
<mat-form-field [ngStyle]="{ display: hidden ? 'none' : '' }">
57+
<mat-label>{{ label }}</mat-label>
58+
<mat-select
59+
(selectionChange)="onSelect($event)"
60+
[id]="id"
61+
[formControl]="form"
62+
[multiple]="multiple"
63+
(focus)="focused = true"
64+
(focusout)="focused = false"
65+
>
66+
<mat-option *ngIf="!multiple"> <i>None</i> </mat-option>
67+
<mat-option
68+
*ngFor="let option of optionElements"
69+
[value]="option.const"
70+
[disabled]="option.disabled ? true : false"
71+
>
72+
{{ option.label }}
73+
</mat-option>
74+
</mat-select>
75+
<mat-hint *ngIf="shouldShowUnfocusedDescription() || focused">{{
76+
description
77+
}}</mat-hint>
78+
<mat-error>{{ error }}</mat-error>
79+
</mat-form-field>
80+
`,
81+
styles: [
82+
`
83+
:host {
84+
display: flex;
85+
flex-direction: row;
86+
}
87+
mat-form-field {
88+
flex: 1 1 auto;
89+
}
90+
`,
91+
],
92+
changeDetection: ChangeDetectionStrategy.OnPush,
93+
})
94+
export class SelectControlRenderer extends JsonFormsControl implements OnInit {
95+
@Input() options: OptionOfSelect[];
96+
multiple = false;
97+
optionElements: OptionOfSelect[] = [];
98+
focused = false;
99+
constructor(jsonformsService: JsonFormsAngularService) {
100+
super(jsonformsService);
101+
}
102+
getEventValue = (event: any) => event.target.value || undefined;
103+
104+
ngOnInit() {
105+
super.ngOnInit();
106+
let scope: JsonSchema = this.scopedSchema;
107+
108+
/* allow multiple selections for array type control */
109+
if (scope.items) {
110+
this.multiple = true;
111+
/* change scope to items of the array */
112+
scope = scope.items as JsonSchema;
113+
}
114+
console.log(scope);
115+
116+
if (this.options) {
117+
this.optionElements = this.options;
118+
} else {
119+
/* Used for enum types */
120+
if (scope.enum) {
121+
this.optionElements = scope.enum.map((el) => {
122+
return { const: el, label: el };
123+
});
124+
}
125+
/* Used for oneOf types */
126+
if (scope.oneOf) {
127+
this.optionElements = scope.oneOf.map((el) => {
128+
return {
129+
const: el.const,
130+
label: el.title ? el.title : el.const,
131+
// disabled: el.readOnly ? true : false,
132+
};
133+
});
134+
}
135+
}
136+
}
137+
138+
onSelect(ev: MatSelectChange) {
139+
const path = composeWithUi(this.uischema as ControlElement, this.path);
140+
this.jsonFormsService.updateCore(Actions.update(path, () => ev.value));
141+
this.triggerValidation();
142+
}
143+
144+
protected getOwnProps(): OwnPropsOfSelect {
145+
return {
146+
...super.getOwnProps(),
147+
options: this.options,
148+
};
149+
}
150+
}
151+
152+
const hasOneOfItems = (schema: JsonSchema): boolean =>
153+
schema.oneOf !== undefined &&
154+
schema.oneOf.length > 0 &&
155+
(schema.oneOf as JsonSchema[]).every((entry: JsonSchema) => {
156+
return entry.const !== undefined;
157+
});
158+
159+
const hasEnumItems = (schema: JsonSchema): boolean =>
160+
schema.type === 'string' && schema.enum !== undefined;
161+
162+
export const SelectControlRendererTester: RankedTester = rankWith(
163+
5,
164+
/* Can be used for simple Enums or OneOf, autocomplete functionallity needs autocomplete.rederer */
165+
or(
166+
and(or(isEnumControl, isOneOfEnumControl), optionIs('autocomplete', false)),
167+
and(
168+
uiTypeIs('Control'),
169+
and(
170+
schemaMatches(
171+
(schema) =>
172+
hasType(schema, 'array') &&
173+
!Array.isArray(schema.items) &&
174+
schema.uniqueItems === true
175+
),
176+
schemaSubPathMatches('items', (schema, rootSchema) => {
177+
const resolvedSchema = schema.$ref
178+
? resolveSchema(rootSchema, schema.$ref, rootSchema)
179+
: schema;
180+
return hasOneOfItems(resolvedSchema) || hasEnumItems(resolvedSchema);
181+
})
182+
)
183+
)
184+
)
185+
);
186+
187+
interface OptionOfSelect {
188+
const: string;
189+
label?: string;
190+
disabled?: boolean;
191+
}
192+
193+
interface OwnPropsOfSelect extends OwnPropsOfControl {
194+
options: OptionOfSelect[];
195+
}

packages/angular-material/src/library/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ import {
4545
RangeControlRenderer,
4646
RangeControlRendererTester,
4747
} from './controls/range.renderer';
48+
import {
49+
SelectControlRenderer,
50+
SelectControlRendererTester,
51+
} from './controls/select.renderer';
4852
import {
4953
DateControlRenderer,
5054
DateControlRendererTester,
@@ -104,6 +108,7 @@ export const angularMaterialRenderers: {
104108
{ tester: TextAreaRendererTester, renderer: TextAreaRenderer },
105109
{ tester: NumberControlRendererTester, renderer: NumberControlRenderer },
106110
{ tester: RangeControlRendererTester, renderer: RangeControlRenderer },
111+
{ tester: SelectControlRendererTester, renderer: SelectControlRenderer },
107112
{ tester: DateControlRendererTester, renderer: DateControlRenderer },
108113
{ tester: ToggleControlRendererTester, renderer: ToggleControlRenderer },
109114
{ tester: enumControlTester, renderer: AutocompleteControlRenderer },

packages/angular-material/src/library/module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { BooleanControlRenderer } from './controls/boolean.renderer';
5050
import { DateControlRenderer } from './controls/date.renderer';
5151
import { NumberControlRenderer } from './controls/number.renderer';
5252
import { RangeControlRenderer } from './controls/range.renderer';
53+
import { SelectControlRenderer } from './controls/select.renderer';
5354
import { TextAreaRenderer } from './controls/textarea.renderer';
5455
import { TextControlRenderer } from './controls/text.renderer';
5556
import { ToggleControlRenderer } from './controls/toggle.renderer';
@@ -96,6 +97,7 @@ import { LayoutChildrenRenderPropsPipe } from './layouts';
9697
TextControlRenderer,
9798
NumberControlRenderer,
9899
RangeControlRenderer,
100+
SelectControlRenderer,
99101
DateControlRenderer,
100102
ToggleControlRenderer,
101103
VerticalLayoutRenderer,

packages/examples/src/examples/enum-multi.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,24 @@ export const uischema = {
5656
type: 'Control',
5757
scope: '#/properties/oneOfMultiEnum',
5858
},
59+
{
60+
type: 'Control',
61+
scope: '#/properties/oneOfMultiEnum',
62+
options: {
63+
autocomplete: false,
64+
},
65+
},
5966
{
6067
type: 'Control',
6168
scope: '#/properties/multiEnum',
6269
},
70+
{
71+
type: 'Control',
72+
scope: '#/properties/multiEnum',
73+
options: {
74+
autocomplete: false,
75+
},
76+
},
6377
],
6478
};
6579

0 commit comments

Comments
 (0)