Skip to content

Commit

Permalink
feat(PAYMENTS-18330): form loaded event
Browse files Browse the repository at this point in the history
  • Loading branch information
ekireevxs committed Apr 17, 2024
1 parent aab4bea commit 7ed4f35
Show file tree
Hide file tree
Showing 16 changed files with 149 additions and 68 deletions.
5 changes: 5 additions & 0 deletions src/core/form/fields-type.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum FieldsType {
text = 'text',
check = 'check',
select = 'select',
}
66 changes: 66 additions & 0 deletions src/core/form/form-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { singleton } from 'tsyringe';
import { Field } from './field.interface';
import { FieldsType } from './fields-type.enum';

@singleton()
export class FormLoader {
private _fields!: { [key: string]: boolean };
private _isAllFieldsLoaded!: Promise<void>;

private _resolve!: () => void;
private _isPromiseResolved = false;

public async setupAndAwaitFieldsLoading(fields: Field[]): Promise<void> {
this._isAllFieldsLoaded = new Promise((resolve) => {
this._resolve = () => {
resolve();
};
});

this._isPromiseResolved = false;
this._fields = {};

const filteredFields = this.filterFields(fields);

const isFormWithoutFields = !filteredFields.length;

if (isFormWithoutFields) {
this._resolve();
this._isPromiseResolved = true;
}

filteredFields.forEach((field) => {
this._fields[field.name] = false;
});

return this._isAllFieldsLoaded;
}

public setFieldLoaded(name: string): void {
if (name in this._fields) {
this._fields[name] = true;
}

if (this.isAllFieldsLoaded && !this._isPromiseResolved) {
this._resolve();
this._isPromiseResolved = true;
}
}

private get isAllFieldsLoaded(): boolean {
return Object.values(this._fields).every((value) => value);
}

private filterFields(fields: Field[]): Field[] {
return fields.filter((field) => {
const isTextControl = field.type === FieldsType.text;
const isCheckboxControl = field.type === FieldsType.check;
const isSelectControl = field.type === FieldsType.select;
const isQrCodeControl = field.name === 'qr';

return (
isTextControl || isCheckboxControl || isSelectControl || isQrCodeControl
);
});
}
}
16 changes: 15 additions & 1 deletion src/core/web-components/web-component.abstract.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { isLoadingCssClassName } from '../../shared/loading-state/is-loading-css-class-name.const';
import { FormLoader } from '../form/form-loader';
import { container } from 'tsyringe';
import { createFinishLoadingEvent } from '../../shared/loading-state/dispatch-finish-loading-event.function';
import { isLoadingCssClassName } from '../../shared/loading-state/is-loading-css-class-name.const';

export abstract class WebComponentAbstract extends HTMLElement {
protected formLoader!: FormLoader;

protected eventListeners: Array<{
element: Element;
eventType: string;
listener(event: Event): void;
}> = [];

public constructor() {
super();
this.formLoader = container.resolve(FormLoader);
}

protected abstract getHtml(): string;

protected connectedCallback(): void {
Expand Down Expand Up @@ -75,6 +84,10 @@ export abstract class WebComponentAbstract extends HTMLElement {
}
}

protected finishLoadingFormControlHandler(componentName: string): void {
this.formLoader.setFieldLoaded(componentName);
}

protected startLoadingComponentHandler(): void {
this.classList.add(isLoadingCssClassName);
}
Expand All @@ -86,5 +99,6 @@ export abstract class WebComponentAbstract extends HTMLElement {

protected dispatchFinishLoadingEvent(componentName: string): void {
this.dispatchEvent(createFinishLoadingEvent(componentName));
this.formLoader.setFieldLoaded(componentName);
}
}
4 changes: 4 additions & 0 deletions src/features/headless-checkout/headless-checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { Themes } from '../../core/customization/themes.type';
import { Lang } from '../../core/i18n/lang.enum';
import { getCountryListHandler } from './post-messages-handlers/get-country-list.handler';
import { CountryResponse } from '../../core/country-response.interface';
import { FormLoader } from '../../core/form/form-loader';

@singleton()
export class HeadlessCheckout {
Expand Down Expand Up @@ -137,6 +138,8 @@ export class HeadlessCheckout {
activate: (): void => {
this.formStatus = FormStatus.active;
},
setupAndAwaitFieldsLoading: async (fields: Field[]): Promise<void> =>
this.formLoader.setupAndAwaitFieldsLoading(fields),
};

public get formConfiguration(): FormConfiguration | undefined {
Expand All @@ -159,6 +162,7 @@ export class HeadlessCheckout {
private readonly headlessCheckoutSpy: HeadlessCheckoutSpy,
private readonly formSpy: FormSpy,
private readonly themesLoader: ThemesLoader,
private readonly formLoader: FormLoader,
) {}

public async init(environment: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export class ApplePayComponent extends SecureComponentAbstract {
private readonly listenApplePayWindowCloseDelay = 100;
private applePayWindow?: Window | null;
private listenApplePayWindowCloseTimeout?: ReturnType<typeof setTimeout>;
private isWaitingPayment = false;

public constructor() {
super();
Expand Down Expand Up @@ -111,7 +110,6 @@ export class ApplePayComponent extends SecureComponentAbstract {
}

private setupWaitingPayment(isWaiting: boolean): void {
this.isWaitingPayment = isWaiting;
if (isWaiting) {
this.drawWaitingElement();
this.hidePayButton();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CheckboxComponent } from './checkbox.component';
import { CheckboxComponentConfig } from './checkbox-component-config.interface';
import { XpsBoolean } from '../../../../core/xps-boolean.enum';
import { FormSpy } from '../../../../core/spy/form-spy/form-spy';
import { FormLoader } from '../../../../core/form/form-loader';

const config: CheckboxComponentConfig = {
name: 'test',
Expand All @@ -29,6 +30,7 @@ describe('CheckboxComponent', () => {
let headlessCheckout: HeadlessCheckout;
let postMessagesClient: PostMessagesClient;
let windowService: Window;
let formLoader: FormLoader;

window.customElements.define(
WebComponentTagName.CheckboxComponent,
Expand All @@ -48,6 +50,11 @@ describe('CheckboxComponent', () => {

windowService = window;

formLoader = {
setupAndAwaitFieldsLoading: noopStub,
setFieldLoaded: noopStub,
} as unknown as FormLoader;

container.clearInstances();

container
Expand All @@ -64,7 +71,8 @@ describe('CheckboxComponent', () => {
},
} as FormSpy,
})
.register<Window>(Window, { useValue: windowService });
.register<Window>(Window, { useValue: windowService })
.register<FormLoader>(FormLoader, { useValue: formLoader });
});

afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ export class CheckboxComponent extends BaseControl<CheckboxComponentConfig> {

void this.getComponentConfig(this.controlName).then((config) => {
this.config = config as CheckboxComponentConfig;

this.render();
this.addEventListenerToElement(this.inputRef, 'change', (event: Event) =>
this.notifyOnValueChanges(event),
);
this.finishLoadingFormControlHandler(this.controlName);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PaymentFormComponent } from './payment-form.component';
import { Field } from '../../../../core/form/field.interface';
import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client';
import { FieldSettings } from './field-settings.interface';
import { FormLoader } from '../../../../core/form/form-loader';

function createComponent(): void {
const element = document.createElement(
Expand Down Expand Up @@ -47,6 +48,7 @@ describe('PaymentFormComponent', () => {
let paymentFormFieldsManager: PaymentFormFieldsService;
let postMessagesClient: PostMessagesClient;
let windowService: Window;
let formLoader: FormLoader;

window.customElements.define(
WebComponentTagName.PaymentFormComponent,
Expand Down Expand Up @@ -77,6 +79,11 @@ describe('PaymentFormComponent', () => {
},
} as unknown as FormSpy;

formLoader = {
setupAndAwaitFieldsLoading: noopStub,
setFieldLoaded: noopStub,
} as unknown as FormLoader;

paymentFormFieldsManager = {
createMissedFields: noopStub,
removeExtraFields: noopStub,
Expand Down Expand Up @@ -104,7 +111,8 @@ describe('PaymentFormComponent', () => {
.register<PostMessagesClient>(PostMessagesClient, {
useValue: postMessagesClient,
})
.register<Window>(Window, { useValue: windowService });
.register<Window>(Window, { useValue: windowService })
.register<FormLoader>(FormLoader, { useValue: formLoader });
});

afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@ export class PaymentFormComponent extends WebComponentAbstract {
return;
}
const formExpectedFields = this.formSpy.formFields;
const formRequriedFields = this.getRequriedFields(this.formSpy.formFields);

const formRequiredFields = this.getRequriedFields(this.formSpy.formFields);

super.render();
if (formExpectedFields) {
this.setupFormLoader(formRequiredFields);

const expectedFields = this.getFieldsSettings(formExpectedFields);
const requiredFields = this.getFieldsSettings(formRequriedFields);
const requiredFields = this.getFieldsSettings(formRequiredFields);
const existsControls = this.getExistsControls();

this.setupFormFields(expectedFields, requiredFields, existsControls);
Expand Down Expand Up @@ -150,4 +153,8 @@ export class PaymentFormComponent extends WebComponentAbstract {
() => null,
);
}

private setupFormLoader(fields: Field[]): void {
void this.formLoader.setupAndAwaitFieldsLoading(fields);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ export class PaymentMethodsComponent extends WebComponentAbstract {
}

protected connectedCallback(): void {
this.startLoadingComponentHandler();

if (!this.headlessCheckoutSpy.appWasInit) {
this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback());
return;
Expand Down Expand Up @@ -103,7 +101,6 @@ export class PaymentMethodsComponent extends WebComponentAbstract {
this.filteredMethods = this.paymentMethods.slice();

super.render();
this.finishLoadingComponentHandler('payment-methods');
this.listenClicks();
this.setupSearch();
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
import { SecureComponentAbstract } from '../../../../core/web-components/secure-component/secure-component.abstract';
import { EventName } from '../../../../core/event-name.enum';
import { finishLoadComponentHandler } from '../../post-messages-handlers/finish-load-component.handler';
import { container } from 'tsyringe';
import { HeadlessCheckout } from '../../headless-checkout';
import { FormSpy } from '../../../../core/spy/form-spy/form-spy';
import { EventName } from '../../../../core/event-name.enum';
import { finishLoadComponentHandler } from '../../post-messages-handlers/finish-load-component.handler';

export class QrCodeComponent extends SecureComponentAbstract {
protected componentName = 'qr-code';
protected inputName = 'qr';
private readonly headlessCheckout: HeadlessCheckout;
private readonly formSpy: FormSpy;

public constructor() {
super();
this.headlessCheckout = container.resolve(HeadlessCheckout);
this.formSpy = container.resolve(FormSpy);

this.headlessCheckout.events.onCoreEvent(
EventName.finishLoadComponent,
finishLoadComponentHandler,
(res) => {
if (res?.fieldName && res?.fieldName === this.inputName) {
this.finishLoadingFormControlHandler(this.inputName);
}
},
);
}

protected connectedCallback(): void {
this.startLoadingComponentHandler();

if (!this.formSpy.formWasInit) {
this.formSpy.listenFormInit(() => this.connectedCallback());
return;
}

this.headlessCheckout.events.onCoreEvent(
EventName.finishLoadComponent,
finishLoadComponentHandler,
() => this.finishLoadingComponentHandler('qr-code'),
);

super.connectedCallback();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export class SavedMethodsComponent extends WebComponentAbstract {
}

protected connectedCallback(): void {
this.startLoadingComponentHandler();
if (!this.headlessCheckoutSpy.appWasInit) {
this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback());
return;
Expand Down Expand Up @@ -98,7 +97,6 @@ export class SavedMethodsComponent extends WebComponentAbstract {
): void => {
this.savedMethods = savedMethods;
super.render();
this.finishLoadingComponentHandler('saved-methods');
this.listenClicks();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SelectAttributes } from './select-attributes.enum';
import { SelectComponent } from './select.component';
import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy';
import { FormSpy } from '../../../../core/spy/form-spy/form-spy';
import { FormLoader } from '../../../../core/form/form-loader';

function createComponent(name: string): HTMLElement {
const element = document.createElement(WebComponentTagName.SelectComponent);
Expand Down Expand Up @@ -37,6 +38,7 @@ describe('SelectComponent', () => {
let postMessagesClient: PostMessagesClient;
let headlessCheckout: HeadlessCheckout;
let headlessCheckoutSpy: HeadlessCheckoutSpy;
let formLoader: FormLoader;

window.customElements.define(
WebComponentTagName.SelectComponent,
Expand All @@ -50,6 +52,11 @@ describe('SelectComponent', () => {
send: noopStub,
} as unknown as PostMessagesClient;

formLoader = {
setupAndAwaitFieldsLoading: noopStub,
setFieldLoaded: noopStub,
} as unknown as FormLoader;

headlessCheckout = {
form: {
onFieldsStatusChange: noopStub,
Expand Down Expand Up @@ -84,6 +91,9 @@ describe('SelectComponent', () => {
})
.register(HeadlessCheckoutSpy, {
useValue: headlessCheckoutSpy,
})
.register(FormLoader, {
useValue: formLoader,
});
});

Expand Down
Loading

0 comments on commit 7ed4f35

Please sign in to comment.