import type { Validation } from '@vuelidate/core'; import type { OptionBuilder } from 'vue-facing-decorator/dist/optionBuilder'; import type { AjaxError } from './api-http'; import type { Language } from './enums/language'; import type { IValidation, ValidationState } from './static-wrappers/interfaces/validation-interface'; import { useVuelidate } from '@vuelidate/core'; import { Vue } from 'vue-facing-decorator'; import { globalState } from '../app/global-state'; import PowerduckState from '../app/powerduck-state'; import NotificationProvider from './../components/ui/notification'; import { TryCallApiResult } from './enums/api'; import StorageProvider from './local-storage-shim'; import ScrollUtils from './scroll-utils'; import { isNullOrEmpty } from './utils/is-null-or-empty'; import { PortalUtils } from './utils/utils'; import { ValidationHelper } from './validation'; export abstract class PowerduckViewModelBase extends Vue { blockRoot: boolean = true; authorized: boolean = true; v$: Validation; constructor(optionBuilder: OptionBuilder, vueInstance: any) { super(optionBuilder, vueInstance); this.v$ = useVuelidate({ $scope: vueInstance, }) as any as Validation; } /** * Current interface language */ get appLanguage(): Language { return PowerduckState.getCurrentLanguage(); } /** * Try GET data from Inviton API enpoint */ public async tryGetDataByArgs>(args: TryCallApiArgs): Promise { return this.tryCallApiByArgs(args, false); } /** * Try POST data to Inviton API endpoint */ public async tryPostDataByArgs(args: TryCallApiArgs): Promise> { const retVal = await this.tryCallApiByArgs(args, true); if (retVal != null && (retVal as any).ajaxErr != null) { return { data: null as any, error: (retVal as any).ajaxErr, result: TryCallApiResult.Error, }; } else { return { data: retVal, error: null as any, result: TryCallApiResult.Success, }; } } /** * Try PATCH data to Inviton API endpoint * just decorator */ public async tryPatchDataByArgs(args: TryCallApiArgs): Promise> { return await this.tryPostDataByArgs(args); } public async tryDeleteDataByArgs(args: TryCallApiArgs): Promise { return this.tryCallApiByArgs(args, false); } private async tryCallApiByArgs(args: TryCallApiArgs, includeError: boolean): Promise { let retVal: TData; let handle: any = null; if (args.blockRoot != false) { handle = setTimeout(() => { this.blockRoot = true; }, 850); } const apiMethod = args.apiMethod as any; const promise = apiMethod(args.requestArgs, args.timeout); try { retVal = await promise; } catch (e: any) { let err: AjaxError = e; if (args.blockRoot != false) { this.blockRoot = false; } if (e == 'not authorized, token expired') { err = { authorized: false, responseText: PowerduckState.getResourceValue('loginExpired'), } as any; } if (err.authorized == false || e == 'not authorized, token expired') { globalState.loginModalRootInstance.show(); } if (!err.authorized && args.toggleAuthorization) { this.authorized = err.authorized; } if (args.showError != false) { const parsedMsg = PowerduckState.parseErrorMessage(err.responseText); if (!isNullOrEmpty(parsedMsg)) { this.showErrorMessage(parsedMsg); } else if (err.responseText) { this.showErrorMessage(err.responseText); } else if ((err as any).message) { this.showErrorMessage((err as any).message); } } if (includeError) { retVal = { ajaxErr: err, } as any; } else { retVal = null as any; } } if (args.blockRoot != false) { try { clearTimeout(handle); } catch (error) { } if (this.blockRoot == true) { this.blockRoot = false; } } return retVal; } /** * Get children components of given type * * @param typeName Name of the type */ getChildrenByType(typeName: string): Array { return PortalUtils.getChildrenByType(this, typeName); } /** * Displays unobtrusive error message * @param errMsg Error message */ showErrorMessage(errMsg: string): void { NotificationProvider.showErrorMessage(errMsg); } /** * Displays unobtrusive success message * @param successMsg Success message */ showSuccessMessage(successMsg: string): void { NotificationProvider.showSuccessMessage(successMsg); } /** * Parse variable into number * @param val * @param defaultValue */ getNumericValue(val: any, defaultValue?: number) { if (val != null) { try { val = Number(val); if (isNaN(val)) { val = defaultValue; } } catch (e) { val = defaultValue; } } else { val = defaultValue; } return val; } /** * Validates current viewModel state based on given valdiation ruleset */ async validate(showErrorMessage?: boolean, silent?: boolean): Promise { if (StorageProvider.getString('disableValidation') == '1') { return true; } if (this.v$ == null) { throw new Error('Validation rules not specified, has to be specified in @Component declaration!'); } const isInvalid = !(await this.v$.$validate()); if (isInvalid) { if (showErrorMessage != false) { this.showValidationErrorMessage(); } if (silent != true) { this.validationIncludeDirty = true; this.$nextTick(() => { this.scrollToFirstPossibleError(this.unwrapRootElement()); }); } } return !isInvalid; } unwrapRootElement(): HTMLElement { if ((this.$el as any).$getChildSlots != null) { return (((this.$el as any).$getChildSlots() || [])[0]?.el || this.$el) as any; } return this.$el as any; } showValidationErrorMessage() { this.showErrorMessage(PowerduckState.getResourceValue('errorsOnForm')); } scrollToFirstPossibleError(context: Element | null) { ScrollUtils.scrollToFirstPossibleError(context); } /** * Scrolls to element * @param elem */ scrollToElem( elem: typeof Vue | Element | typeof Vue[] | Element[], mobileOffset?: boolean | number, mobileOffsetSmoothing?: boolean, animated?: boolean, instant?: boolean, ): void { ScrollUtils.scrollToElem( elem, mobileOffset, mobileOffsetSmoothing, animated, instant, ); } /** * Scrolls to position */ scrollToPos( position: number, context?: HTMLElement | null, animated?: boolean, instant?: boolean, ): void { ScrollUtils.scrollToPos( position, context, animated, instant, ); } /** * Determines if DIRTY should be included in validation */ validationIncludeDirty: boolean = false; /** * Obtains validation state of given property * @param valProp Validation property */ validationStateOf(valProp: IValidation | IValidation[], customMessage?: string): ValidationState { let retVal: ValidationState = null as any; if (!PortalUtils.isArray(valProp)) { retVal = ValidationHelper.getValidationDisplayState(valProp as any, this.validationIncludeDirty); } else { let validationResult: ValidationState; for (let i = 0, len = (valProp as IValidation[]).length; i < len; i++) { validationResult = ValidationHelper.getValidationDisplayState(valProp[i], this.validationIncludeDirty); if (!validationResult.valid) { retVal = validationResult; break; } } } if (retVal?.valid == false) { if (!isNullOrEmpty(customMessage as any)) { retVal = retVal || {} as any; retVal.errorMessage = customMessage as string; } } return retVal; } /** * Resets validation state of the viewModel */ resetValidation() { ValidationHelper.resetValidation(this); this.validationIncludeDirty = false; } } interface TryCallApiArgs { apiMethod: (data?: TArgs) => Promise; timeout?: number; requestArgs?: TArgs; showError?: boolean; blockRoot?: boolean; toggleAuthorization?: boolean; toggleAuthorizationOnNullUser?: boolean; } export interface TryPostApiResponse { data: TData; result: TryCallApiResult; error: AjaxError; } export interface TryPatchApiResponse extends TryPostApiResponse { }