Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(PAYMENTS-18930): form loaded event #91

Merged
merged 1 commit into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/core/actions/show-qr-code.action.type.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Action } from './action.interface';
import { Field } from '../form/field.interface';

export type ShowQrCodeActionType = 'show_qr_code';

export interface ShowQrCodeActionData {
fields: Field[];
submitButtonText: string;
}

Expand Down
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
Loading
Loading