Skip to content

Commit 9b8d23f

Browse files
committed
feat(ui-library): validation in withoutslot using ElementInternals
1 parent e84fe8d commit 9b8d23f

File tree

5 files changed

+148
-79
lines changed

5 files changed

+148
-79
lines changed

packages/ui-library/src/components/checkbox/index.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class BlrCheckbox extends LitElement {
4242
@property() label!: string;
4343
@property() checkInputId?: string = '';
4444
@property() arialabel?: string;
45-
45+
@property() required?: boolean;
4646
@property() disabled?: boolean;
4747
@property() checked?: boolean;
4848
@property() indeterminate?: boolean;
@@ -65,6 +65,22 @@ export class BlrCheckbox extends LitElement {
6565
@state() protected currentCheckedState: boolean | undefined = this.checked;
6666
@state() protected currentIndeterminateState: boolean | undefined = this.indeterminate;
6767

68+
// TESTING BEGIN
69+
// Identify the element as a form-associated custom element
70+
static formAssociated = true;
71+
private _internals: ElementInternals;
72+
73+
constructor() {
74+
super();
75+
// Get access to the internal form control APIs
76+
this._internals = this.attachInternals();
77+
}
78+
79+
public checkValidity() {
80+
return this._internals.checkValidity;
81+
}
82+
// TESTING END
83+
6884
protected updated(changedProperties: Map<string, boolean>) {
6985
if (changedProperties.has('checked')) {
7086
this.currentCheckedState = this.checked || false;
@@ -78,21 +94,6 @@ export class BlrCheckbox extends LitElement {
7894
}
7995
}
8096

81-
connectedCallback() {
82-
super.connectedCallback();
83-
addEventListener('propChanged', this._onPropChanged);
84-
}
85-
86-
disconnectedCallback() {
87-
super.disconnectedCallback();
88-
removeEventListener('propChanged', this._onPropChanged);
89-
}
90-
91-
_onPropChanged = (event: any) => {
92-
this.hasError = event.detail.hasError;
93-
this.errorMessage = event.detail.errorMessage;
94-
};
95-
9697
protected handleChange(event: Event) {
9798
if (!this.disabled && !this.readonly) {
9899
this.currentIndeterminateState = false;
@@ -277,10 +278,11 @@ export class BlrCheckbox extends LitElement {
277278
id=${this.checkInputId || nothing}
278279
name=${this.name || nothing}
279280
?disabled=${this.disabled}
280-
?checked=${this.currentCheckedState}
281+
?checked=${this.checked}
281282
?indeterminate=${this.currentIndeterminateState}
282283
?readonly=${this.readonly}
283284
?hasError=${this.hasError}
285+
?required="${this.required}"
284286
@change=${this.handleChange}
285287
aria-hidden="true"
286288
/>

packages/ui-library/src/components/form-example-without-slot/index.ts

Lines changed: 84 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,22 @@ import { property, query } from 'lit/decorators.js';
66
export class BlrFormExampleWithoutSlot extends LitElement {
77
@property() theme: ThemeType = 'Light';
88
@property() firstInputValue: string = '';
9+
@property() firstTextInputHasError: boolean = false;
10+
@property() secondTextInputHasError: boolean = false;
11+
@property() radioHasError: boolean = false;
912
@property() secondInputValue: string = '';
10-
@property() errorMessage: string = '';
11-
@property() firstNameHasError?: boolean = false;
12-
@property() lastNameHasError?: boolean = false;
13-
@property({ reflect: true }) checkBoxChecked: boolean = false;
13+
@property() checkInputHasError?: boolean = false;
14+
@property() selectHasError?: boolean = false;
15+
@property() checkBoxChecked: boolean = false;
16+
@property() textInputErrorMessage: string = 'input field required';
17+
@property() selectErrorMessage: string = 'at least one selection required';
18+
@property() radioErrorMessage: string = 'at least one selection required';
1419

1520
@query('blr-text-input[name="firstInput"]') firstInputElement!: HTMLElement;
1621
@query('blr-text-input[name="secondInput"]') secondInputElement!: HTMLElement;
1722
@query('blr-checkbox[name="checkInput"]') checkboxInputElement!: HTMLElement;
23+
@query('blr-select[name="select"]') selectElement!: HTMLElement;
24+
@query('blr-radio[name="radio"]') radioElement!: HTMLElement;
1825

1926
protected render() {
2027
return html`
@@ -36,6 +43,8 @@ export class BlrFormExampleWithoutSlot extends LitElement {
3643
showinputicon="true"
3744
@blrTextValueChange="${this.handleFirstInputChange}"
3845
required="true"
46+
.hasError=${this.firstTextInputHasError}
47+
errorMessage=${this.textInputErrorMessage}
3948
></blr-text-input>
4049
<blr-text-input
4150
size="md"
@@ -53,23 +62,59 @@ export class BlrFormExampleWithoutSlot extends LitElement {
5362
inputicon="blr360"
5463
showinputicon="true"
5564
@blrTextValueChange="${this.handleSecondInputChange}"
65+
.hasError=${this.secondTextInputHasError}
66+
errorMessage=${this.textInputErrorMessage}
5667
required="true"
5768
></blr-text-input>
69+
<blr-select
70+
theme="Light"
71+
size="md"
72+
label="Select one"
73+
labelappendix=""
74+
icon=""
75+
hinticon="blrInfo"
76+
.hasError=${this.selectHasError}
77+
errormessage=${this.selectErrorMessage}
78+
errormessageicon="blrError"
79+
arialabel="Select"
80+
selectid="selectId"
81+
name="select"
82+
haslabel="true"
83+
required="true"
84+
>
85+
<option value="" label="--Please choose an option--"></option>
86+
<option value="option1" label="Option 1"></option>
87+
<option value="option2" label="Option 2"></option>
88+
</blr-select>
89+
<blr-radio
90+
theme="Light"
91+
size="md"
92+
value=""
93+
label="Hint and error message"
94+
.hasError=${this.radioHasError}
95+
errormessage=${this.radioErrorMessage}
96+
arialabel=""
97+
erroricon="blrErrorFilled"
98+
optionid="optionId"
99+
name="radio"
100+
required="true"
101+
></blr-radio>
58102
<blr-checkbox
59103
theme="Light"
60104
size="md"
61105
checkedicon="blrCheckmark"
62106
indeterminatedicon="blrMinus"
63107
haslabel="true"
64108
label="I accept the terms and conditions."
65-
hintmessage="This is a small hint message"
66-
hinticon="blrInfo"
67109
erroricon=""
68110
arialabel="check Input"
69111
checkinputid="checkInputId"
70112
name="checkInput"
71113
@blrCheckedChange=${this.handleCheckInput}
72-
?checked=${this.checkBoxChecked}
114+
.hasError=${this.checkInputHasError}
115+
errorMessage="must be checked"
116+
required="true"
117+
.checked=${this.checkBoxChecked}
73118
></blr-checkbox>
74119
<blr-text-button
75120
theme="Light"
@@ -88,52 +133,41 @@ export class BlrFormExampleWithoutSlot extends LitElement {
88133

89134
private handleSubmit(event) {
90135
event.preventDefault();
136+
const shadowFirstInputElement = this.firstInputElement.shadowRoot?.querySelector('input[name="firstInput"]');
137+
const shadowSecondInputElement = this.secondInputElement.shadowRoot?.querySelector('input[name="secondInput"]');
138+
const shadowCheckInputElement = this.checkboxInputElement.shadowRoot?.querySelector('input[name="checkInput"]');
139+
const shadowSelectElement = this.selectElement.shadowRoot?.querySelector('select[name="select"]');
140+
const shadowRadioElement = this.radioElement.shadowRoot?.querySelector('input[name="radio"]');
91141

92-
if (
93-
(this.secondInputElement.hasAttribute('required') && this.secondInputValue.trim() === '') ||
94-
(this.secondInputElement.hasAttribute('required') && this.secondInputValue.trim() === '') ||
95-
!this.checkboxInputElement.hasAttribute('checked')
96-
) {
97-
if (this.firstInputElement.hasAttribute('required') && this.firstInputValue.trim() === '') {
98-
this.firstInputElement._onPropChanged({
99-
detail: {
100-
hasError: true,
101-
errorMessage: 'This is a required field',
102-
},
103-
});
104-
}
105-
106-
if (this.secondInputElement.hasAttribute('required') && this.secondInputValue.trim() === '') {
107-
this.secondInputElement._onPropChanged({
108-
detail: {
109-
hasError: true,
110-
errorMessage: 'This is a required field',
111-
},
112-
});
113-
}
114-
115-
if (!this.checkboxInputElement.hasAttribute('checked')) {
116-
this.checkboxInputElement._onPropChanged({
117-
detail: {
118-
hasError: true,
119-
errorMessage: 'This is a required field',
120-
},
121-
});
122-
}
123-
console.log('Please provide a value for both first input and last input fields and check the checkbox.');
124-
// just to simulate the value change. Remove later
125-
setTimeout(() => {
126-
this.dispatchEvent(
127-
new CustomEvent('propChanged', {
128-
detail: { hasError: false, errorMessage: '' },
129-
bubbles: true,
130-
composed: true,
131-
})
132-
);
133-
}, 3000);
134-
return;
142+
if (!shadowFirstInputElement?.checkValidity()) {
143+
this.firstTextInputHasError = true;
135144
}
136145

146+
if (!shadowSecondInputElement?.checkValidity()) {
147+
this.secondTextInputHasError = true;
148+
}
149+
150+
if (!shadowCheckInputElement?.checkValidity()) {
151+
this.checkInputHasError = true;
152+
}
153+
154+
if (!shadowSelectElement?.checkValidity()) {
155+
this.selectHasError = true;
156+
}
157+
158+
if (!shadowRadioElement?.checkValidity()) {
159+
this.radioHasError = true;
160+
}
161+
162+
// just to simulate the value change. Remove later
163+
setTimeout(() => {
164+
this.firstTextInputHasError = false;
165+
this.secondTextInputHasError = false;
166+
this.checkInputHasError = false;
167+
this.selectHasError = false;
168+
this.radioHasError = false;
169+
}, 2000);
170+
137171
console.log(
138172
`First Input: ${this.firstInputValue}, Second Input: ${this.secondInputValue}, checkbox checked: ${this.checkBoxChecked}`
139173
);

packages/ui-library/src/components/radio/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ export class BlrRadio extends LitElement {
3535

3636
@property() theme: ThemeType = 'Light';
3737

38+
// TESTING BEGIN
39+
// Identify the element as a form-associated custom element
40+
static formAssociated = true;
41+
private _internals: ElementInternals;
42+
43+
constructor() {
44+
super();
45+
// Get access to the internal form control APIs
46+
this._internals = this.attachInternals();
47+
}
48+
49+
public checkValidity() {
50+
return this._internals.checkValidity;
51+
}
52+
// TESTING END
53+
3854
protected render() {
3955
if (this.size) {
4056
const dynamicStyles = this.theme === 'Light' ? [formLight, radioLight] : [formDark, radioDark];

packages/ui-library/src/components/select/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ export class BlrSelect extends LitElement {
5151

5252
@state() protected isFocused = false;
5353

54+
// TESTING BEGIN
55+
// Identify the element as a form-associated custom element
56+
static formAssociated = true;
57+
private _internals: ElementInternals;
58+
59+
constructor() {
60+
super();
61+
// Get access to the internal form control APIs
62+
this._internals = this.attachInternals();
63+
}
64+
65+
public checkValidity() {
66+
return this._internals.checkValidity;
67+
}
68+
// TESTING END
69+
5470
protected _optionElements: Element[] | undefined;
5571

5672
protected handleFocus = () => {

packages/ui-library/src/components/text-input/index.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,21 @@ export class BlrTextInput extends LitElement {
6969
@state() protected currentType: InputTypes = this.type;
7070
@state() protected isFocused = false;
7171

72-
connectedCallback() {
73-
super.connectedCallback();
74-
addEventListener('propChanged', this._onPropChanged);
75-
}
72+
// TESTING BEGIN
73+
// Identify the element as a form-associated custom element
74+
static formAssociated = true;
75+
private _internals: ElementInternals;
7676

77-
disconnectedCallback() {
78-
super.disconnectedCallback();
79-
removeEventListener('propChanged', this._onPropChanged);
77+
constructor() {
78+
super();
79+
// Get access to the internal form control APIs
80+
this._internals = this.attachInternals();
8081
}
8182

82-
_onPropChanged = (event: any) => {
83-
this.hasError = event.detail.hasError;
84-
this.errorMessage = event.detail.errorMessage;
85-
};
83+
public checkValidity() {
84+
return this._internals.checkValidity;
85+
}
86+
// TESTING END
8687

8788
protected togglePassword = () => {
8889
this.currentType = this.currentType === 'password' ? 'text' : 'password';
@@ -211,7 +212,7 @@ export class BlrTextInput extends LitElement {
211212
@blur=${this.handleBlur}
212213
@focus=${this.handleFocus}
213214
maxlength="${this.maxLength}"
214-
pattern="${this.pattern}"
215+
pattern="${this.pattern || nothing}"
215216
@select=${this.handleSelect}
216217
/>
217218
</div>

0 commit comments

Comments
 (0)