import type { PowerduckViewModelBase } from './base-component'; import type { IValidationSet, ValidationRuleset, ValidationState } from './static-wrappers/interfaces/validation-interface'; import { useVuelidate, type Validation } from '@vuelidate/core'; import { alpha, alphaNum, between, decimal, email, helpers, integer, ipAddress, macAddress, maxLength, maxValue, minLength, minValue, numeric, requiredUnless, url, } from '@vuelidate/validators'; import { reactive, unref } from 'vue'; import PowerduckState from '../app/powerduck-state'; import { capitalize } from './extensions/string-extensions'; import LocalizedValueHelper from './localized-value-helper'; import { isNullOrEmpty } from './utils/is-null-or-empty'; const getFirstUnsattisfiedValidatorName = (valProp: Validation): string | null => { const errors = valProp?.$errors || []; for (const fieldError of errors) { if (fieldError != null) { return fieldError.$validator; } } return null; }; const getErrorMessage = (valProp: Validation, invalidValidatorName: string): string | null => { if (invalidValidatorName == null) { return null; } if (invalidValidatorName == 'requiredIf' || invalidValidatorName == 'requiredLocalizedString' || invalidValidatorName == 'requiredTemporal') { invalidValidatorName = 'required'; } let errMsg = PowerduckState.getResourceValue(`validationError${invalidValidatorName[capitalize]()}` as any); switch (invalidValidatorName) { case 'minLength': case 'minValue': errMsg = errMsg.replace('{0}', valProp[invalidValidatorName].$params.min); break; case 'maxLength': case 'maxValue': errMsg = errMsg.replace('{0}', valProp[invalidValidatorName].$params.max); break; case 'between': errMsg = errMsg.replace('{0}', valProp[invalidValidatorName].$params.min).replace('{1}', valProp[invalidValidatorName].$params.max); break; default: } if (isNullOrEmpty(errMsg)) { errMsg = PowerduckState.getResourceValue('validationErrorGeneric'); } return errMsg; }; export class ValidationHelper { static getValidationDisplayState(valProp: Validation, includeDirty: boolean): ValidationState { let errMsg: string; let invalidValidatorName: string; if (valProp.$invalid && (includeDirty || valProp._changed == true)) { invalidValidatorName = getFirstUnsattisfiedValidatorName(valProp); errMsg = getErrorMessage(valProp, invalidValidatorName); } return { mandatory: valProp.required != null, valid: invalidValidatorName == null, validationDeclaration: valProp, errorMessage: errMsg, }; } static resetValidation(viewModel: PowerduckViewModelBase): void { const $v = viewModel.v$; const recIterate = (obj) => { for (const key in obj) { if (!key.startsWith('$')) { const objItem = obj[key]; if (objItem != null) { if (Object.getPrototypeOf(objItem) === Object.prototype) { recIterate(objItem); } if (objItem._changed == true) { objItem._changed = false; } } } } if (obj.$reset) { obj.$reset(); } if (obj._changed == true) { obj._changed = false; } }; if ($v != null) { try { $v.value.$reset(); } catch (e) { } try { recIterate($v); } catch (e) { } } viewModel.$forceUpdate(); } static transformRuleset(ruleSet: ValidationRuleset): any { return ruleSet as any; } static validateModel(ruleSet: ValidationRuleset, item: any): Promise<{ valid: boolean; v$: Validation; }> { const state = reactive(item); const v$ = useVuelidate(ruleSet, state).value; v$.$touch(); return new Promise((resolve) => { if (!v$.$pending) { resolve({ valid: !v$.$invalid, v$, }); return; } v$.$validate().then((res) => { resolve({ valid: res, v$, }); }); }); } } const customReq = (value: any): boolean => { value = unref(value); if (Array.isArray(value)) { return !!value.length; } if (value === undefined || value === null) { return false; } if (value === false) { return true; } if (value instanceof Date) { return !isNaN(value.getTime()); } if (typeof Temporal !== 'undefined') { if (value instanceof Temporal.PlainDateTime || value instanceof Temporal.PlainDate || value instanceof Temporal.ZonedDateTime || value instanceof Temporal.Instant) { return true; } } if (typeof value === 'object') { // eslint-disable-next-line no-unreachable-loop for (const _ in value) { return true; } return false; } return !!String(value).length; }; export class ValidationBuilder { private _validationArgs: any = {}; static get() { return new ValidationBuilder(); } build(): IValidationSet { return this._validationArgs as any; } required(): ValidationBuilder { this._validationArgs.required = customReq; return this; } requiredIf(field: string | ((vm: any, parentVm?: PowerduckViewModelBase) => boolean)): ValidationBuilder { const requireIf = (propOrFunction: boolean | string | ((...args: any[]) => boolean)) => { const validate = (prop: any, val: any) => prop ? customReq(typeof val === 'string' ? val.trim() : val) : true; return function ( this: any, value: any, parentVM: any, ) { if (typeof propOrFunction != 'function') { return validate((propOrFunction), value); } const result = propOrFunction.call( this, value, parentVM, ); return validate(result, value); }; }; this._validationArgs.requiredIf = requireIf(field); return this; } requiredLocalizedString(): ValidationBuilder { this._validationArgs.requiredLocalizedString = (value) => { if (value == null || isNullOrEmpty(LocalizedValueHelper.getValue(value, 'sk'))) { return false; } return true; }; return this; } requiredUnless?(field: string | ((vm: any, parentVm?: PowerduckViewModelBase) => any)): ValidationBuilder { this._validationArgs.requiredUnless = requiredUnless(field as string); return this; } minLength(length: number): ValidationBuilder { this._validationArgs.minLength = minLength(length); return this; } maxLength(length: number): ValidationBuilder { this._validationArgs.maxLength = maxLength(length); return this; } minValue(length: number): ValidationBuilder { this._validationArgs.minValue = minValue(length); return this; } maxValue(length: number): ValidationBuilder { this._validationArgs.maxValue = maxValue(length); return this; } between(min: number, max: number): ValidationBuilder { this._validationArgs.between = between(min, max); return this; } alpha(): ValidationBuilder { this._validationArgs.alpha = alpha; return this; } alphaNum(): ValidationBuilder { this._validationArgs.alphaNum = alphaNum; return this; } numeric(): ValidationBuilder { this._validationArgs.numeric = numeric; return this; } integer(): ValidationBuilder { this._validationArgs.integer = integer; return this; } decimal(): ValidationBuilder { this._validationArgs.decimal = decimal; return this; } email(): ValidationBuilder { this._validationArgs.email = email; return this; } ipAddress(): ValidationBuilder { this._validationArgs.ipAddress = ipAddress; return this; } macAddress(): ValidationBuilder { this._validationArgs.macAddress = macAddress; return this; } sameAs(field: any | ((vm: any, parentVm?: PowerduckViewModelBase) => any)): ValidationBuilder { this._validationArgs.sameAs = ( value: any, vm: any, parentVm: PowerduckViewModelBase, ) => { const fieldValue = typeof field === 'function' ? field(vm, parentVm) : field; return value === fieldValue; }; return this; } url(): ValidationBuilder { this._validationArgs.url = url; return this; } customRule(name: string, validationFunc: (value: string) => boolean): ValidationBuilder { this._validationArgs[name] = validationFunc; return this; } withCustomErrorMessage(msg: string): ValidationBuilder { this._validationArgs.customErrMsg = helpers.withParams({ type: 'customErrMsg', errMsg: msg }, (_value, _parentVm) => true); return this; } }