Skip to content

Commit 3a20d3c

Browse files
Carola Bratschcbratsch
authored andcommitted
feat: add formly phone number input with basic validator (#778 #424)
1 parent 7e662ad commit 3a20d3c

File tree

17 files changed

+142
-18
lines changed

17 files changed

+142
-18
lines changed

docs/guides/formly.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ Refer to the tables below for an overview of these parts.
232232
| ish-checkbox-field | Basic checkbox input | ---- |
233233
| ish-email-field | Text input field that automatically adds an e-mail validator and error message | ---- |
234234
| ish-password-field | Password input field that automatically adds a password validator and error message | ---- |
235+
| ish-phone-field | Phone number input field that automatically adds a phone number validator and error messages | ---- |
235236
| ish-fieldset-field | Wraps fields in a `<fieldset>` tag for styling | `fieldsetClass`: Class that will be added to the fieldset tag. `childClass`: Class that will be added to the child div. |
236237
| ish-captcha-field | Includes the `<ish-lazy-captcha>` component and adds the relevant `formControls` to the form | `topic`: Topic that will be passed to the Captcha component. |
237238
| ish-radio-field | Basic radio input | ---- |

e2e/cypress/integration/specs/account/contact.b2c.e2e-spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const _ = {
88
password: '!InterShop00!',
99
fullName: 'Patricia Miller',
1010
formContent: {
11-
phone: '12345',
11+
phone: '12345678',
1212
subject: 'Returns',
1313
comment: "Don't fit.",
1414
},

projects/organization-management/src/app/components/user-profile-form/user-profile-form.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export class UserProfileFormComponent implements OnInit {
117117
},
118118
{
119119
key: 'phoneHome',
120-
type: 'ish-text-input-field',
120+
type: 'ish-phone-field',
121121
templateOptions: {
122122
label: 'account.profile.phone.label',
123123
},

src/app/core/services/registration-form-configuration/registration-form-configuration.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ export class RegistrationFormConfigurationService {
350350
},
351351
{
352352
key: 'phoneHome',
353-
type: 'ish-text-input-field',
353+
type: 'ish-phone-field',
354354
templateOptions: {
355355
label: 'account.profile.phone.label',
356356
required: false,

src/app/pages/account-profile-user/account-profile-user/account-profile-user.component.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ describe('Account Profile User Component', () => {
4141
it('should display 3 input fields for firstName, lastName and phone', () => {
4242
fixture.detectChanges();
4343

44-
expect(element.innerHTML.match(/ish-text-input-field/g)).toHaveLength(3);
44+
expect(element.innerHTML.match(/ish-text-input-field/g)).toHaveLength(2);
45+
expect(element.innerHTML.match(/ish-phone-field/g)).toHaveLength(1);
4546

4647
expect(element.innerHTML).toContain('firstName');
4748
expect(element.innerHTML).toContain('lastName');

src/app/pages/account-profile-user/account-profile-user/account-profile-user.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class AccountProfileUserComponent implements OnInit {
8888
},
8989
{
9090
key: 'phoneHome',
91-
type: 'ish-text-input-field',
91+
type: 'ish-phone-field',
9292
templateOptions: {
9393
label: 'account.update_profile.phone.label',
9494
},

src/app/pages/contact/contact-form/contact-form.component.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,11 @@ export class ContactFormComponent implements OnInit {
6969
},
7070
{
7171
key: 'phone',
72-
type: 'ish-text-input-field',
72+
type: 'ish-phone-field',
7373
templateOptions: {
7474
label: 'helpdesk.contactus.phone.label',
7575
required: true,
7676
},
77-
validation: {
78-
messages: {
79-
required: 'helpdesk.contactus.phone.error',
80-
},
81-
},
8277
},
8378
{
8479
key: 'order',

src/app/shared/formly-address-forms/configurations/address-form.configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ const standardFields: { [key: string]: Omit<FormlyFieldConfig, 'key'> } = {
114114
},
115115
},
116116
phoneHome: {
117-
type: 'ish-text-input-field',
117+
type: 'ish-phone-field',
118118
templateOptions: {
119119
label: 'account.profile.phone.label',
120120
required: false,

src/app/shared/formly/dev/testing/formly-testing.module.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class TextInputFieldComponent extends FieldType {}
2828
@Component({ template: 'EmailFieldComponent: {{ field.key }} {{ field.type }} {{ to | json }}' })
2929
class EmailFieldComponent extends FieldType {}
3030

31+
@Component({ template: 'PhoneFieldComponent: {{ field.key }} {{ field.type }} {{ to | json }}' })
32+
class PhoneFieldComponent extends FieldType {}
33+
3134
@Component({ template: 'PasswordFieldComponent: {{ field.key }} {{ field.type }} {{ to | json }}' })
3235
class PasswordFieldComponent extends FieldType {}
3336

@@ -53,6 +56,7 @@ class DummyWrapperComponent extends FieldWrapper {}
5356
EmailFieldComponent,
5457
FieldsetFieldComponent,
5558
PasswordFieldComponent,
59+
PhoneFieldComponent,
5660
RadioFieldComponent,
5761
SelectFieldComponent,
5862
TextInputFieldComponent,
@@ -82,6 +86,10 @@ class DummyWrapperComponent extends FieldWrapper {}
8286
name: 'ish-password-field',
8387
component: PasswordFieldComponent,
8488
},
89+
{
90+
name: 'ish-phone-field',
91+
component: PhoneFieldComponent,
92+
},
8593
{
8694
name: 'ish-select-field',
8795
component: SelectFieldComponent,

src/app/shared/formly/formly.module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { CheckboxFieldComponent } from './types/checkbox-field/checkbox-field.co
2323
import { EmailFieldComponent } from './types/email-field/email-field.component';
2424
import { FieldsetFieldComponent } from './types/fieldset-field/fieldset-field.component';
2525
import { PasswordFieldComponent } from './types/password-field/password-field.component';
26+
import { PhoneFieldComponent } from './types/phone-field/phone-field.component';
2627
import { RadioFieldComponent } from './types/radio-field/radio-field.component';
2728
import { SelectFieldComponent } from './types/select-field/select-field.component';
2829
import { TextInputFieldComponent } from './types/text-input-field/text-input-field.component';
@@ -53,6 +54,11 @@ import { ValidationWrapperComponent } from './wrappers/validation-wrapper/valida
5354
component: EmailFieldComponent,
5455
wrappers: ['form-field-horizontal', 'validation'],
5556
},
57+
{
58+
name: 'ish-phone-field',
59+
component: PhoneFieldComponent,
60+
wrappers: ['form-field-horizontal', 'validation'],
61+
},
5662
{
5763
name: 'ish-password-field',
5864
component: PasswordFieldComponent,
@@ -125,6 +131,7 @@ import { ValidationWrapperComponent } from './wrappers/validation-wrapper/valida
125131
HorizontalWrapperComponent,
126132
InputAddonWrapperComponent,
127133
PasswordFieldComponent,
134+
PhoneFieldComponent,
128135
RadioFieldComponent,
129136
RadioHorizontalWrapperComponent,
130137
SelectFieldComponent,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<input
2+
type="tel"
3+
[formControl]="formControl"
4+
class="form-control"
5+
[formlyAttributes]="field"
6+
[attr.data-testing-id]="field.key"
7+
[maxlength]="to?.maxlength ? to.maxlength : 20"
8+
/>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
3+
import { FormlyModule } from '@ngx-formly/core';
4+
5+
import { FormlyTestingComponentsModule } from 'ish-shared/formly/dev/testing/formly-testing-components.module';
6+
import { FormlyTestingContainerComponent } from 'ish-shared/formly/dev/testing/formly-testing-container/formly-testing-container.component';
7+
8+
import { PhoneFieldComponent } from './phone-field.component';
9+
10+
describe('Phone Field Component', () => {
11+
let component: FormlyTestingContainerComponent;
12+
let fixture: ComponentFixture<FormlyTestingContainerComponent>;
13+
let element: HTMLElement;
14+
15+
beforeEach(async () => {
16+
await TestBed.configureTestingModule({
17+
declarations: [PhoneFieldComponent],
18+
imports: [
19+
FormlyModule.forRoot({
20+
types: [{ name: 'phone', component: PhoneFieldComponent }],
21+
}),
22+
FormlyTestingComponentsModule,
23+
ReactiveFormsModule,
24+
],
25+
}).compileComponents();
26+
});
27+
28+
beforeEach(() => {
29+
const testComponentInputs = {
30+
model: { phone: '' },
31+
fields: [
32+
{
33+
key: 'phone',
34+
type: 'phone',
35+
},
36+
],
37+
form: new FormGroup({}),
38+
};
39+
fixture = TestBed.createComponent(FormlyTestingContainerComponent);
40+
component = fixture.componentInstance;
41+
element = fixture.nativeElement;
42+
43+
component.testComponentInputs = testComponentInputs;
44+
});
45+
46+
it('should be created', () => {
47+
expect(component).toBeTruthy();
48+
expect(element).toBeTruthy();
49+
expect(() => fixture.detectChanges()).not.toThrow();
50+
});
51+
52+
it('should be rendered after creation', () => {
53+
fixture.detectChanges();
54+
expect(element.querySelector('ish-phone-field')).toBeTruthy();
55+
});
56+
57+
it('should be invalid if an incorrect phone is entered', () => {
58+
fixture.detectChanges();
59+
component.form.get('phone').setValue('123456a');
60+
fixture.detectChanges();
61+
expect(component.form.get('phone').valid).toBeFalsy();
62+
});
63+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ChangeDetectionStrategy, Component } from '@angular/core';
2+
import { FormControl } from '@angular/forms';
3+
import { FieldType, FormlyFieldConfig } from '@ngx-formly/core';
4+
5+
import { SpecialValidators } from 'ish-shared/forms/validators/special-validators';
6+
7+
@Component({
8+
selector: 'ish-phone-field',
9+
templateUrl: './phone-field.component.html',
10+
changeDetection: ChangeDetectionStrategy.OnPush,
11+
})
12+
export class PhoneFieldComponent extends FieldType {
13+
formControl: FormControl;
14+
15+
prePopulate(field: FormlyFieldConfig) {
16+
field.validators = field.validators ?? {};
17+
field.validators.validation = [...(field.validators.validation ?? []), SpecialValidators.phone];
18+
19+
field.validation = field.validation ?? {};
20+
field.validation.messages = {
21+
...field.validation?.messages,
22+
phone: 'form.phone.error.invalid',
23+
required: 'form.phone.error.required',
24+
};
25+
}
26+
}

src/app/shared/forms/validators/special-validators.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@ export class SpecialValidators {
4040
: { email: true };
4141
}
4242

43+
static phone(control: FormControl) {
44+
/*
45+
* simplified phone matching
46+
* - phone number must start with + or digit
47+
* - number blocks can be separated with hyphens or spaces
48+
* - number blocks can stand in brackets
49+
* - phone number must have 7 to 15 digits
50+
*/
51+
return control.value
52+
? /^((?:\+?\d{7,15})$)|^((\(?\d{3}\)?(?: |-)?){2}\(?\d{3,4}\)?)$/.test(control.value)
53+
? undefined
54+
: { phone: true }
55+
: undefined;
56+
}
57+
4358
/**
4459
* Compare two form controls for equality.
4560
*

src/assets/i18n/de_DE.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,6 @@
409409
"account.user.new.order_spend_limit.error": "Geben Sie ein gültiges Ausgabelimit pro Bestellung an.",
410410
"account.user.new.order_spend_limit.error.money": "Geben Sie ein gültiges Ausgabelimit pro Bestellung an.",
411411
"account.user.new.order_spend_limit.label": "Ausgabelimit pro Bestellung",
412-
"account.user.new.phone.error.valid": "Bitte geben Sie eine gültige Telefonnummer an.",
413412
"account.user.update_budget.heading": "Budget Limits bearbeiten",
414413
"account.user.update_profile.heading": "Benutzerprofil bearbeiten",
415414
"account.user.update_roles.heading": "Rollen des Benutzers bearbeiten",
@@ -718,6 +717,8 @@
718717
"error.page.title": "Seite nicht gefunden!",
719718
"footer.content": "<div class=\"footer\"><div class=\"container\"><div class=\"row\"><p class=\"disclaimer\"><strong>Disclaimer: </strong>Wir weisen daraufhin, dass dieser inSPIRED Demo Shop ein reiner Demonstrationsshop ist und s&auml;mtliche Inhalte lediglich beispielhaften Charakter zum Zwecke der Demonstration und Illustration haben. F&uuml;r die Richtigkeit, Vollst&auml;ndigkeit und Aktualit&auml;t der enthaltenen Inhalte &uuml;bernimmt Intershop Communications AG keine Haftung. Die Inhalte des Demo-Shops d&uuml;rfen nicht in Ihren Shop &uuml;bernommen oder anderweitig von Ihnen genutzt werden.</p></div><div class=\"row\"><div class=\"col-md-4 col-lg-2\"><h4 class=\"d-md-none link-group-title\" (click)=\"collapsed[0] = !collapsed[0]\"><span style=\"text-transform: none;\">in</span> SPIRED<fa-icon class=\"link-group-icon\" [icon]=\"['fas', 'angle-down']\"></fa-icon></h4><h4 class=\"d-none d-md-block\"><span style=\"text-transform: none;\">in</span>SPIRED</h4><div class=\"link-group\" [ngbCollapse]=\"collapsed[0]\"><ul><li><a href=\"page://page.aboutus\">&Uuml;ber uns</a></li><li><a href=\"page://page.termsAndConditions.pagelet2-Page\">Allgemeine Gesch&auml;ftsbedingungen</a></li><li><a href=\"page://page.privacyPolicy.pagelet2-Page\">Datenschutzhinweise</a></li><li><a href=\"route://cookies\" title=\"Cookies verwalten\">Cookies verwalten</a></li><li><a href=\"page://page.helpdesk.pagelet2-Page\">Hilfe</a></li><li><a href=\"route://contact\">Kontakt</a></li></ul></div></div><div class=\"col-md-4 col-lg-2\"><h4 class=\"d-md-none link-group-title\" (click)=\"collapsed[1] = !collapsed[1]\">SERVICE<fa-icon class=\"link-group-icon\" [icon]=\"['fas', 'angle-down']\"></fa-icon></h4><h4 class=\"d-none d-md-block\">SERVICE</h4><div class=\"link-group\" [ngbCollapse]=\"collapsed[1]\"><ul><li><a href=\"page://page.helpdesk.faq\">H&auml;ufige Fragen</a></li><li><a href=\"page://page.helpdesk.orderingShippingPayment\">Bestellung, Versand und Bezahlung</a></li><li><a href=\"page://page.returnpolicy\">R&uuml;ckgaberegelung</a></li><li><a href=\"page://page.helpdesk.giftServices\">Geschenk-Services</a></li><li><a href=\"page://page.helpdesk.safetySecurityPrivacy\">Sicherheit, Schutz &amp; Privatsph&auml;re</a></li></ul></div></div><div class=\"col-md-4 col-lg-2\"><h4 class=\"d-md-none link-group-title\" (click)=\"collapsed[2] = !collapsed[2]\">MEIN KONTO<fa-icon class=\"link-group-icon\" [icon]=\"['fas', 'angle-down']\"></fa-icon></h4><h4 class=\"d-none d-md-block\">MEIN KONTO</h4><div class=\"link-group\" [ngbCollapse]=\"collapsed[2]\"><ul><li><a rel=\"nofollow\" href=\"route://account/orders\">Bestellhistorie</a></li><li><a rel=\"nofollow\" href=\"\">Geschenkkarten und -gutscheine</a></li><li><a rel=\"nofollow\" href=\"\">Geschenkkarten-Guthaben</a></li><li><a rel=\"nofollow\" href=\"\">Gespeicherte Zahlungsdaten</a></li><li><a rel=\"nofollow\" href=\"route://account/addresses\">Gespeicherte Adressen</a></li><li><a rel=\"nofollow\" href=\"route://account/profile\">Profileinstellungen</a></li><li><a rel=\"nofollow\" href=\"\">Produktbenachrichtigungen</a></li></ul></div></div><div class=\"col-md-4 col-lg-2\"><h4 class=\"d-md-none link-group-title\" (click)=\"collapsed[3] = !collapsed[3]\">BELIEBTE KATEGORIEN<fa-icon class=\"link-group-icon\" [icon]=\"['fas', 'angle-down']\"></fa-icon></h4><h4 class=\"d-none d-md-block\">BELIEBTE KATEGORIEN</h4><div class=\"link-group\" [ngbCollapse]=\"collapsed[3]\"><ul><li><a href=\"route://Action-Kameras-catCameras-Camcorders.832\">Action-Kameras</a></li><li><a href=\"route://TVs-Projektoren-catHome-Entertainment.220\">TVs &amp; Projektoren</a></li><li><a href=\"route://Monitors-catComputers.3001\">Monitore</a></li><li><a href=\"route://Heimkino-Systeme-catHome-Entertainment.1058.1082\">Heimkino-Systeme</a></li><li><a href=\"route://Tablets-catComputers.897\">Tablets</a></li><li><a href=\"route://Notebooks-catComputers.1835.151\">Notebooks</a></li></ul></div></div><div class=\"col-md-4 col-lg-2\"><h4 class=\"d-md-none link-group-title\" (click)=\"collapsed[4] = !collapsed[4]\">BELIEBTE PRODUKTE<fa-icon class=\"link-group-icon\" [icon]=\"['fas', 'angle-down']\"></fa-icon></h4><h4 class=\"d-none d-md-block\">BELIEBTE PRODUKTE</h4><div class=\"link-group\" [ngbCollapse]=\"collapsed[4]\"><ul><li><a href=\"product://8182790134362\">GoPro Hero 4 Silver</a></li><li><a href=\"product://8806086011815\">Samsung Full HD 3D Smart TV</a></li><li><a href=\"product://3953312\">Canon LEGRIA HF R16</a></li><li><a href=\"product://8806086070188\">Samsung Galaxy NotePRO 4G </a></li><li><a href=\"product://689796\">Wrist Rest Fellowes</a></li></ul></div></div><div class=\"col-md-4 col-lg-2\"><h4 class=\"d-md-none link-group-title\" (click)=\"collapsed[5] = !collapsed[5]\">DEMO CONTENT<fa-icon class=\"link-group-icon\" [icon]=\"['fas', 'angle-down']\"></fa-icon></h4><h4 class=\"d-none d-md-block\">DEMO CONTENT</h4><div class=\"link-group\" [ngbCollapse]=\"collapsed[5]\"><ul><li><a href=\"https://icecat.biz/\"target=\"_blank\"title=\"Demo Content provided by Icecat\"class=\"cms-footer-longLinks\">Icecat Content</a></li><li><a href=\"page://page.democontent\" title=\"Demo Content Disclaimer\">Demo Disclaimer</a></li><li><a href=\"route://error\" title=\"Error Page\">Fehler</a></li><li><a href=\"route://home\" title=\"Root Route\">Hauptseite</a></li><li><a href=\"route://basket\"title=\"Basket Page\">Warenkorb</a></li></ul></div></div></div><div class=\"row text-center\"><p class=\"copyright\">Intershop Progressive Web App {{ appVersion }} <br />&copy; 2021 INTERSHOP Communications AG. Alle Rechte vorbehalten.</p></div></div></div>",
720719
"forgot_password.form.send.button.label": "Senden",
720+
"form.phone.error.invalid": "Bitte geben Sie eine gültige Telefonnummer an.",
721+
"form.phone.error.required": "Bitte geben Sie Ihre Telefonnummer an.",
721722
"header.welcome.text": "Willkommen bei inSPIRED",
722723
"helpdesk.contact_us.heading": "Kontakt",
723724
"helpdesk.contact_us.link": "Kontakt",
@@ -730,7 +731,6 @@
730731
"helpdesk.contactus.name.error": "Bitte geben Sie Ihren Namen an.",
731732
"helpdesk.contactus.name.label": "Name (Vor- und Nachname)",
732733
"helpdesk.contactus.order.label": "Bestellnummer",
733-
"helpdesk.contactus.phone.error": "Bitte geben Sie Ihre Telefonnummer an.",
734734
"helpdesk.contactus.phone.label": "Telefon",
735735
"helpdesk.contactus.prolog": "Sie können innerhalb von zwei Werktagen mit einer Antwort rechnen.",
736736
"helpdesk.contactus.subject.error": "Bitte wählen Sie einen Betreff.",

0 commit comments

Comments
 (0)