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 16, 2024
1 parent aab4bea commit d5ef2a8
Show file tree
Hide file tree
Showing 17 changed files with 141 additions and 114 deletions.
61 changes: 61 additions & 0 deletions src/core/form/form-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { singleton } from 'tsyringe';
import { Field } from './field.interface';

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

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

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

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

const filteredFields = this.filterFields(fields);

if (!filteredFields.length) {
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 === 'text';
const isCheckboxControl = field.type === 'check';
const isSelectControl = field.type === 'select';
const isQrCode = field.name === 'qr';

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

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,16 +82,7 @@ export abstract class WebComponentAbstract extends HTMLElement {
}
}

protected startLoadingComponentHandler(): void {
this.classList.add(isLoadingCssClassName);
}

protected finishLoadingComponentHandler(componentName: string): void {
this.classList.remove(isLoadingCssClassName);
this.dispatchFinishLoadingEvent(componentName);
}

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;
},
setupFieldsToTrackLoading: async (fields: Field[]): Promise<void> =>
this.formLoader.setupFieldsToTrackLoading(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 @@ -8,7 +8,6 @@ import { errorsHtmlWrapperClassName } from './errors-html-wrapper-classname.cons
import { applePayErrors } from './apple-pay.errors.const';
import i18next from 'i18next';
import { FormSpy } from '../../../../core/spy/form-spy/form-spy';
import { finishLoadComponentHandler } from '../../post-messages-handlers/finish-load-component.handler';
import { headlessCheckoutAppUrl } from '../../environment';
import { openApplePayPageHandler } from '../../post-messages-handlers/apple-pay/open-apple-pay-page.handler';
import { Message } from '../../../../core/message.interface';
Expand Down Expand Up @@ -59,21 +58,9 @@ export class ApplePayComponent extends SecureComponentAbstract {
}
},
);

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

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

if (!this.formSpy.formWasInit) {
this.formSpy.listenFormInit(() => this.connectedCallback());
return;
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 = {
setupFieldsToTrackLoading: 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.finishLoadingComponentHandler(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 = {
setupFieldsToTrackLoading: 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.setupFieldsToTrackLoading(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,12 +1,11 @@
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';

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

Expand All @@ -17,22 +16,26 @@ export class QrCodeComponent extends SecureComponentAbstract {
}

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();
this.addEventListeners();
}

protected getHtml(): string {
return this.getSecureHtml();
}

private addEventListeners(): void {
this.addEventListenerToElement(
this.querySelector('iframe') as unknown as HTMLIFrameElement,
'load',
() => {
this.finishLoadingComponentHandler(this.inputName);
},
);
}
}
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 = {
setupFieldsToTrackLoading: 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 d5ef2a8

Please sign in to comment.