Skip to content

Commit

Permalink
feat: add formly phone number input with basic validator (#778 #424)
Browse files Browse the repository at this point in the history
  • Loading branch information
Carola Bratsch authored and cbratsch committed Jul 21, 2021
1 parent 7e662ad commit 3a20d3c
Show file tree
Hide file tree
Showing 17 changed files with 142 additions and 18 deletions.
1 change: 1 addition & 0 deletions docs/guides/formly.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ Refer to the tables below for an overview of these parts.
| ish-checkbox-field | Basic checkbox input | ---- |
| ish-email-field | Text input field that automatically adds an e-mail validator and error message | ---- |
| ish-password-field | Password input field that automatically adds a password validator and error message | ---- |
| ish-phone-field | Phone number input field that automatically adds a phone number validator and error messages | ---- |
| 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. |
| 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. |
| ish-radio-field | Basic radio input | ---- |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const _ = {
password: '!InterShop00!',
fullName: 'Patricia Miller',
formContent: {
phone: '12345',
phone: '12345678',
subject: 'Returns',
comment: "Don't fit.",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class UserProfileFormComponent implements OnInit {
},
{
key: 'phoneHome',
type: 'ish-text-input-field',
type: 'ish-phone-field',
templateOptions: {
label: 'account.profile.phone.label',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ export class RegistrationFormConfigurationService {
},
{
key: 'phoneHome',
type: 'ish-text-input-field',
type: 'ish-phone-field',
templateOptions: {
label: 'account.profile.phone.label',
required: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ describe('Account Profile User Component', () => {
it('should display 3 input fields for firstName, lastName and phone', () => {
fixture.detectChanges();

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

expect(element.innerHTML).toContain('firstName');
expect(element.innerHTML).toContain('lastName');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class AccountProfileUserComponent implements OnInit {
},
{
key: 'phoneHome',
type: 'ish-text-input-field',
type: 'ish-phone-field',
templateOptions: {
label: 'account.update_profile.phone.label',
},
Expand Down
7 changes: 1 addition & 6 deletions src/app/pages/contact/contact-form/contact-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,11 @@ export class ContactFormComponent implements OnInit {
},
{
key: 'phone',
type: 'ish-text-input-field',
type: 'ish-phone-field',
templateOptions: {
label: 'helpdesk.contactus.phone.label',
required: true,
},
validation: {
messages: {
required: 'helpdesk.contactus.phone.error',
},
},
},
{
key: 'order',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const standardFields: { [key: string]: Omit<FormlyFieldConfig, 'key'> } = {
},
},
phoneHome: {
type: 'ish-text-input-field',
type: 'ish-phone-field',
templateOptions: {
label: 'account.profile.phone.label',
required: false,
Expand Down
8 changes: 8 additions & 0 deletions src/app/shared/formly/dev/testing/formly-testing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class TextInputFieldComponent extends FieldType {}
@Component({ template: 'EmailFieldComponent: {{ field.key }} {{ field.type }} {{ to | json }}' })
class EmailFieldComponent extends FieldType {}

@Component({ template: 'PhoneFieldComponent: {{ field.key }} {{ field.type }} {{ to | json }}' })
class PhoneFieldComponent extends FieldType {}

@Component({ template: 'PasswordFieldComponent: {{ field.key }} {{ field.type }} {{ to | json }}' })
class PasswordFieldComponent extends FieldType {}

Expand All @@ -53,6 +56,7 @@ class DummyWrapperComponent extends FieldWrapper {}
EmailFieldComponent,
FieldsetFieldComponent,
PasswordFieldComponent,
PhoneFieldComponent,
RadioFieldComponent,
SelectFieldComponent,
TextInputFieldComponent,
Expand Down Expand Up @@ -82,6 +86,10 @@ class DummyWrapperComponent extends FieldWrapper {}
name: 'ish-password-field',
component: PasswordFieldComponent,
},
{
name: 'ish-phone-field',
component: PhoneFieldComponent,
},
{
name: 'ish-select-field',
component: SelectFieldComponent,
Expand Down
7 changes: 7 additions & 0 deletions src/app/shared/formly/formly.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { CheckboxFieldComponent } from './types/checkbox-field/checkbox-field.co
import { EmailFieldComponent } from './types/email-field/email-field.component';
import { FieldsetFieldComponent } from './types/fieldset-field/fieldset-field.component';
import { PasswordFieldComponent } from './types/password-field/password-field.component';
import { PhoneFieldComponent } from './types/phone-field/phone-field.component';
import { RadioFieldComponent } from './types/radio-field/radio-field.component';
import { SelectFieldComponent } from './types/select-field/select-field.component';
import { TextInputFieldComponent } from './types/text-input-field/text-input-field.component';
Expand Down Expand Up @@ -53,6 +54,11 @@ import { ValidationWrapperComponent } from './wrappers/validation-wrapper/valida
component: EmailFieldComponent,
wrappers: ['form-field-horizontal', 'validation'],
},
{
name: 'ish-phone-field',
component: PhoneFieldComponent,
wrappers: ['form-field-horizontal', 'validation'],
},
{
name: 'ish-password-field',
component: PasswordFieldComponent,
Expand Down Expand Up @@ -125,6 +131,7 @@ import { ValidationWrapperComponent } from './wrappers/validation-wrapper/valida
HorizontalWrapperComponent,
InputAddonWrapperComponent,
PasswordFieldComponent,
PhoneFieldComponent,
RadioFieldComponent,
RadioHorizontalWrapperComponent,
SelectFieldComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<input
type="tel"
[formControl]="formControl"
class="form-control"
[formlyAttributes]="field"
[attr.data-testing-id]="field.key"
[maxlength]="to?.maxlength ? to.maxlength : 20"
/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import { FormlyModule } from '@ngx-formly/core';

import { FormlyTestingComponentsModule } from 'ish-shared/formly/dev/testing/formly-testing-components.module';
import { FormlyTestingContainerComponent } from 'ish-shared/formly/dev/testing/formly-testing-container/formly-testing-container.component';

import { PhoneFieldComponent } from './phone-field.component';

describe('Phone Field Component', () => {
let component: FormlyTestingContainerComponent;
let fixture: ComponentFixture<FormlyTestingContainerComponent>;
let element: HTMLElement;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [PhoneFieldComponent],
imports: [
FormlyModule.forRoot({
types: [{ name: 'phone', component: PhoneFieldComponent }],
}),
FormlyTestingComponentsModule,
ReactiveFormsModule,
],
}).compileComponents();
});

beforeEach(() => {
const testComponentInputs = {
model: { phone: '' },
fields: [
{
key: 'phone',
type: 'phone',
},
],
form: new FormGroup({}),
};
fixture = TestBed.createComponent(FormlyTestingContainerComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;

component.testComponentInputs = testComponentInputs;
});

it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => fixture.detectChanges()).not.toThrow();
});

it('should be rendered after creation', () => {
fixture.detectChanges();
expect(element.querySelector('ish-phone-field')).toBeTruthy();
});

it('should be invalid if an incorrect phone is entered', () => {
fixture.detectChanges();
component.form.get('phone').setValue('123456a');
fixture.detectChanges();
expect(component.form.get('phone').valid).toBeFalsy();
});
});
26 changes: 26 additions & 0 deletions src/app/shared/formly/types/phone-field/phone-field.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { FieldType, FormlyFieldConfig } from '@ngx-formly/core';

import { SpecialValidators } from 'ish-shared/forms/validators/special-validators';

@Component({
selector: 'ish-phone-field',
templateUrl: './phone-field.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PhoneFieldComponent extends FieldType {
formControl: FormControl;

prePopulate(field: FormlyFieldConfig) {
field.validators = field.validators ?? {};
field.validators.validation = [...(field.validators.validation ?? []), SpecialValidators.phone];

field.validation = field.validation ?? {};
field.validation.messages = {
...field.validation?.messages,
phone: 'form.phone.error.invalid',
required: 'form.phone.error.required',
};
}
}
15 changes: 15 additions & 0 deletions src/app/shared/forms/validators/special-validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ export class SpecialValidators {
: { email: true };
}

static phone(control: FormControl) {
/*
* simplified phone matching
* - phone number must start with + or digit
* - number blocks can be separated with hyphens or spaces
* - number blocks can stand in brackets
* - phone number must have 7 to 15 digits
*/
return control.value
? /^((?:\+?\d{7,15})$)|^((\(?\d{3}\)?(?: |-)?){2}\(?\d{3,4}\)?)$/.test(control.value)
? undefined
: { phone: true }
: undefined;
}

/**
* Compare two form controls for equality.
*
Expand Down
4 changes: 2 additions & 2 deletions src/assets/i18n/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,6 @@
"account.user.new.order_spend_limit.error": "Geben Sie ein gültiges Ausgabelimit pro Bestellung an.",
"account.user.new.order_spend_limit.error.money": "Geben Sie ein gültiges Ausgabelimit pro Bestellung an.",
"account.user.new.order_spend_limit.label": "Ausgabelimit pro Bestellung",
"account.user.new.phone.error.valid": "Bitte geben Sie eine gültige Telefonnummer an.",
"account.user.update_budget.heading": "Budget Limits bearbeiten",
"account.user.update_profile.heading": "Benutzerprofil bearbeiten",
"account.user.update_roles.heading": "Rollen des Benutzers bearbeiten",
Expand Down Expand Up @@ -718,6 +717,8 @@
"error.page.title": "Seite nicht gefunden!",
"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>",
"forgot_password.form.send.button.label": "Senden",
"form.phone.error.invalid": "Bitte geben Sie eine gültige Telefonnummer an.",
"form.phone.error.required": "Bitte geben Sie Ihre Telefonnummer an.",
"header.welcome.text": "Willkommen bei inSPIRED",
"helpdesk.contact_us.heading": "Kontakt",
"helpdesk.contact_us.link": "Kontakt",
Expand All @@ -730,7 +731,6 @@
"helpdesk.contactus.name.error": "Bitte geben Sie Ihren Namen an.",
"helpdesk.contactus.name.label": "Name (Vor- und Nachname)",
"helpdesk.contactus.order.label": "Bestellnummer",
"helpdesk.contactus.phone.error": "Bitte geben Sie Ihre Telefonnummer an.",
"helpdesk.contactus.phone.label": "Telefon",
"helpdesk.contactus.prolog": "Sie können innerhalb von zwei Werktagen mit einer Antwort rechnen.",
"helpdesk.contactus.subject.error": "Bitte wählen Sie einen Betreff.",
Expand Down
Loading

0 comments on commit 3a20d3c

Please sign in to comment.