Skip to content

Commit 3f7d65d

Browse files
committed
angular-material: Add OneOfEnumControlRenderer
1 parent 815ba6e commit 3f7d65d

5 files changed

Lines changed: 735 additions & 0 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ export * from './range.renderer';
3030
export * from './date.renderer';
3131
export * from './toggle.renderer';
3232
export * from './enum.renderer';
33+
export * from './one-of-enum.renderer';
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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 type { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
32+
import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular';
33+
import {
34+
Actions,
35+
composeWithUi,
36+
ControlElement,
37+
EnumOption,
38+
isOneOfEnumControl,
39+
JsonFormsState,
40+
mapStateToOneOfEnumControlProps,
41+
OwnPropsOfControl,
42+
OwnPropsOfEnum,
43+
RankedTester,
44+
rankWith,
45+
StatePropsOfControl,
46+
} from '@jsonforms/core';
47+
import type { Observable } from 'rxjs';
48+
import { map, startWith } from 'rxjs/operators';
49+
import { CommonModule } from '@angular/common';
50+
import { ReactiveFormsModule } from '@angular/forms';
51+
import { MatFormFieldModule } from '@angular/material/form-field';
52+
import { MatInputModule } from '@angular/material/input';
53+
import { MatAutocompleteModule } from '@angular/material/autocomplete';
54+
55+
@Component({
56+
selector: 'OneOfEnumControlRenderer',
57+
template: `
58+
<mat-form-field [ngStyle]="{ display: hidden ? 'none' : '' }">
59+
<mat-label>{{ label }}</mat-label>
60+
<input
61+
matInput
62+
type="text"
63+
(change)="onChange($event)"
64+
[id]="id"
65+
[formControl]="form"
66+
[matAutocomplete]="auto"
67+
(keydown)="updateFilter($event)"
68+
(focus)="focused = true"
69+
(focusout)="focused = false"
70+
/>
71+
<mat-autocomplete
72+
autoActiveFirstOption
73+
#auto="matAutocomplete"
74+
(optionSelected)="onSelect($event)"
75+
[displayWith]="displayFn"
76+
>
77+
@for (option of filteredOptions | async; track option.value) {
78+
<mat-option [value]="option">
79+
{{ option.label }}
80+
</mat-option>
81+
}
82+
</mat-autocomplete>
83+
<mat-hint *ngIf="shouldShowUnfocusedDescription() || focused">{{
84+
description
85+
}}</mat-hint>
86+
<mat-error>{{ error }}</mat-error>
87+
</mat-form-field>
88+
`,
89+
styles: [
90+
`
91+
:host {
92+
display: flex;
93+
flex-direction: row;
94+
}
95+
mat-form-field {
96+
flex: 1 1 auto;
97+
}
98+
`,
99+
],
100+
changeDetection: ChangeDetectionStrategy.OnPush,
101+
imports: [
102+
CommonModule,
103+
ReactiveFormsModule,
104+
MatFormFieldModule,
105+
MatInputModule,
106+
MatAutocompleteModule,
107+
],
108+
})
109+
export class OneOfEnumControlRenderer
110+
extends JsonFormsControl
111+
implements OnInit
112+
{
113+
@Input() options?: EnumOption[];
114+
valuesToTranslatedOptions?: Map<string, EnumOption>;
115+
filteredOptions: Observable<EnumOption[]>;
116+
shouldFilter: boolean;
117+
focused = false;
118+
119+
constructor(jsonformsService: JsonFormsAngularService) {
120+
super(jsonformsService);
121+
}
122+
123+
protected override mapToProps(
124+
state: JsonFormsState
125+
): StatePropsOfControl & OwnPropsOfEnum {
126+
return mapStateToOneOfEnumControlProps(state, this.getOwnProps());
127+
}
128+
129+
getEventValue = (event: any) => event.target.value;
130+
131+
override onChange(ev: any) {
132+
const eventValue = this.getEventValue(ev);
133+
const option = Array.from(
134+
this.valuesToTranslatedOptions?.values() ?? []
135+
).find((option) => option.label === eventValue);
136+
if (!option) {
137+
super.onChange(ev);
138+
return;
139+
}
140+
141+
this.jsonFormsService.updateCore(
142+
Actions.update(this.propsPath, () => option.value)
143+
);
144+
this.triggerValidation();
145+
}
146+
147+
ngOnInit() {
148+
super.ngOnInit();
149+
this.shouldFilter = false;
150+
this.filteredOptions = this.form.valueChanges.pipe(
151+
startWith(''),
152+
map((val) => this.filter(val))
153+
);
154+
}
155+
156+
override mapAdditionalProps(_props: StatePropsOfControl & OwnPropsOfEnum) {
157+
this.valuesToTranslatedOptions = new Map(
158+
(_props.options ?? []).map((option) => [option.value, option])
159+
);
160+
}
161+
162+
updateFilter(event: any) {
163+
// ENTER
164+
if (event.keyCode === 13) {
165+
this.shouldFilter = false;
166+
} else {
167+
this.shouldFilter = true;
168+
}
169+
}
170+
171+
onSelect(ev: MatAutocompleteSelectedEvent) {
172+
const path = composeWithUi(this.uischema as ControlElement, this.path);
173+
this.shouldFilter = false;
174+
const option: EnumOption = ev.option.value;
175+
this.jsonFormsService.updateCore(Actions.update(path, () => option.value));
176+
this.triggerValidation();
177+
}
178+
179+
// use arrow function to bind "this" reference
180+
displayFn = (option?: string | EnumOption): string => {
181+
if (!option) {
182+
return '';
183+
}
184+
185+
if (typeof option === 'string') {
186+
if (!this.valuesToTranslatedOptions) {
187+
return option; // show raw value until translations are ready
188+
}
189+
190+
// if no option matches, it is a manual input
191+
return this.valuesToTranslatedOptions.get(option)?.label ?? option;
192+
}
193+
194+
return option?.label ?? '';
195+
};
196+
197+
filter(val: string | EnumOption | undefined): EnumOption[] {
198+
const options = Array.from(this.valuesToTranslatedOptions?.values() || []);
199+
200+
if (!val || !this.shouldFilter) {
201+
return options;
202+
}
203+
204+
const label = typeof val === 'string' ? val : val.label;
205+
return options.filter((option) =>
206+
option.label.toLowerCase().startsWith(label.toLowerCase())
207+
);
208+
}
209+
protected getOwnProps(): OwnPropsOfControl & OwnPropsOfEnum {
210+
return {
211+
...super.getOwnProps(),
212+
options: this.options,
213+
};
214+
}
215+
}
216+
217+
export const oneOfEnumControlTester: RankedTester = rankWith(
218+
5,
219+
isOneOfEnumControl
220+
);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ import {
5757
EnumControlRenderer,
5858
enumControlTester,
5959
} from './controls/enum.renderer';
60+
import {
61+
OneOfEnumControlRenderer,
62+
oneOfEnumControlTester,
63+
} from './controls/one-of-enum.renderer';
6064
import {
6165
ObjectControlRenderer,
6266
ObjectControlRendererTester,
@@ -107,6 +111,7 @@ export const angularMaterialRenderers: {
107111
{ tester: DateControlRendererTester, renderer: DateControlRenderer },
108112
{ tester: ToggleControlRendererTester, renderer: ToggleControlRenderer },
109113
{ tester: enumControlTester, renderer: EnumControlRenderer },
114+
{ tester: oneOfEnumControlTester, renderer: OneOfEnumControlRenderer },
110115
{ tester: ObjectControlRendererTester, renderer: ObjectControlRenderer },
111116
// layouts
112117
{ tester: verticalLayoutTester, renderer: VerticalLayoutRenderer },

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { BooleanControlRenderer } from './controls/boolean.renderer';
4949
import { DateControlRenderer } from './controls/date.renderer';
5050
import { EnumControlRenderer } from './controls/enum.renderer';
5151
import { NumberControlRenderer } from './controls/number.renderer';
52+
import { OneOfEnumControlRenderer } from './controls/one-of-enum.renderer';
5253
import { RangeControlRenderer } from './controls/range.renderer';
5354
import { TextAreaRenderer } from './controls/textarea.renderer';
5455
import { TextControlRenderer } from './controls/text.renderer';
@@ -105,6 +106,7 @@ import { LayoutChildrenRenderPropsPipe } from './layouts';
105106
JsonFormsDetailComponent,
106107
ObjectControlRenderer,
107108
EnumControlRenderer,
109+
OneOfEnumControlRenderer,
108110
TableRenderer,
109111
ArrayLayoutRenderer,
110112
LayoutChildrenRenderPropsPipe,
@@ -145,6 +147,7 @@ import { LayoutChildrenRenderPropsPipe } from './layouts';
145147
JsonFormsDetailComponent,
146148
ObjectControlRenderer,
147149
EnumControlRenderer,
150+
OneOfEnumControlRenderer,
148151
TableRenderer,
149152
ArrayLayoutRenderer,
150153
LayoutChildrenRenderPropsPipe,

0 commit comments

Comments
 (0)