UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

831 lines (829 loc) • 29.6 kB
/** * DevExtreme (cjs/__internal/ui/m_validation_engine.js) * Version: 24.2.6 * Build date: Mon Mar 17 2025 * * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _message = _interopRequireDefault(require("../../common/core/localization/message")); var _number = _interopRequireDefault(require("../../common/core/localization/number")); var _class = _interopRequireDefault(require("../../core/class")); var _errors = _interopRequireDefault(require("../../core/errors")); var _events_strategy = require("../../core/events_strategy"); var _common = require("../../core/utils/common"); var _deferred = require("../../core/utils/deferred"); var _extend = require("../../core/utils/extend"); var _iterator = require("../../core/utils/iterator"); var _type = require("../../core/utils/type"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e } } const EMAIL_VALIDATION_REGEX = /^[\d\w.+_-]+@[\d\w._-]+\.[\w]+$/i; const STATUS = { valid: "valid", invalid: "invalid", pending: "pending" }; class BaseRuleValidator { constructor() { this.NAME = "base" } defaultMessage(value) { return _message.default.getFormatter(`validation-${this.NAME}`)(value) } defaultFormattedMessage(value) { return _message.default.getFormatter(`validation-${this.NAME}-formatted`)(value) } _isValueEmpty(value) { return !rulesValidators.required.validate(value, {}) } validate(value, rule) { const valueArray = Array.isArray(value) ? value : [value]; let result = true; if (valueArray.length) { valueArray.every((itemValue => { result = this._validate(itemValue, rule); return result })) } else { result = this._validate(null, rule) } return result } } class RequiredRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "required" } _validate(value, rule) { if (!(0, _type.isDefined)(value)) { return false } if (false === value) { return false } value = String(value); if (rule.trim || !(0, _type.isDefined)(rule.trim)) { value = value.trim() } return "" !== value } } class NumericRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "numeric" } _validate(value, rule) { if (false !== rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } if (rule.useCultureSettings && (0, _type.isString)(value)) { return !isNaN(_number.default.parse(value)) } return (0, _type.isNumeric)(value) } } class RangeRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "range" } _validate(value, rule) { if (false !== rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } const validNumber = rulesValidators.numeric.validate(value, rule); const validValue = (0, _type.isDefined)(value) && "" !== value; const number = validNumber ? parseFloat(value) : validValue && value.valueOf(); const { min: min } = rule; const { max: max } = rule; if (!(validNumber || (0, _type.isDate)(value)) && !validValue) { return false } if ((0, _type.isDefined)(min)) { if ((0, _type.isDefined)(max)) { return number >= min && number <= max } return number >= min } if ((0, _type.isDefined)(max)) { return number <= max } throw _errors.default.Error("E0101") } } class StringLengthRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "stringLength" } _validate(value, rule) { value = String(value ?? ""); if (rule.trim || !(0, _type.isDefined)(rule.trim)) { value = value.trim() } if (rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } return rulesValidators.range.validate(value.length, (0, _extend.extend)({}, rule)) } } class CustomRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "custom" } validate(value, rule) { if (rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } const { validator: validator } = rule; const dataGetter = validator && (0, _type.isFunction)(validator.option) && validator.option("dataGetter"); const extraParams = (0, _type.isFunction)(dataGetter) && dataGetter(); const params = { value: value, validator: validator, rule: rule }; if (extraParams) { (0, _extend.extend)(params, extraParams) } return rule.validationCallback(params) } } class AsyncRuleValidator extends CustomRuleValidator { constructor() { super(); this.NAME = "async" } validate(value, rule) { if (!(0, _type.isDefined)(rule.reevaluate)) { (0, _extend.extend)(rule, { reevaluate: true }) } if (rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } const { validator: validator } = rule; const dataGetter = validator && (0, _type.isFunction)(validator.option) && validator.option("dataGetter"); const extraParams = (0, _type.isFunction)(dataGetter) && dataGetter(); const params = { value: value, validator: validator, rule: rule }; if (extraParams) { (0, _extend.extend)(params, extraParams) } const callbackResult = rule.validationCallback(params); if (!(0, _type.isPromise)(callbackResult)) { throw _errors.default.Error("E0103") } return this._getWrappedPromise((0, _deferred.fromPromise)(callbackResult).promise()) } _getWrappedPromise(promise) { const deferred = (0, _deferred.Deferred)(); promise.then((res => { deferred.resolve(res) }), (err => { const res = { isValid: false }; if ((0, _type.isDefined)(err)) { if ((0, _type.isString)(err)) { res.message = err } else if ((0, _type.isObject)(err) && (0, _type.isDefined)(err.message) && (0, _type.isString)(err.message)) { res.message = err.message } } deferred.resolve(res) })); return deferred.promise() } } class CompareRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "compare" } _validate(value, rule) { if (!rule.comparisonTarget) { throw _errors.default.Error("E0102") } if (rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true }(0, _extend.extend)(rule, { reevaluate: true }); const otherValue = rule.comparisonTarget(); const type = rule.comparisonType || "=="; switch (type) { case "==": return value == otherValue; case "!=": return value != otherValue; case "===": return value === otherValue; case "!==": return value !== otherValue; case ">": return value > otherValue; case ">=": return value >= otherValue; case "<": return value < otherValue; case "<=": return value <= otherValue } } } class PatternRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "pattern" } _validate(value, rule) { if (false !== rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } let { pattern: pattern } = rule; if ((0, _type.isString)(pattern)) { pattern = new RegExp(pattern) } return pattern.test(value) } } class EmailRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "email" } _validate(value, rule) { if (false !== rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } return rulesValidators.pattern.validate(value, (0, _extend.extend)({}, rule, { pattern: EMAIL_VALIDATION_REGEX })) } } const rulesValidators = { required: new RequiredRuleValidator, numeric: new NumericRuleValidator, range: new RangeRuleValidator, stringLength: new StringLengthRuleValidator, custom: new CustomRuleValidator, async: new AsyncRuleValidator, compare: new CompareRuleValidator, pattern: new PatternRuleValidator, email: new EmailRuleValidator }; class GroupConfig extends(_class.default.inherit({})) { ctor(group, isRemovable) { this.group = group; this.validators = []; this._isRemovable = isRemovable; this._pendingValidators = []; this._onValidatorStatusChanged = this._onValidatorStatusChanged.bind(this); this._resetValidationInfo(); this._eventsStrategy = new _events_strategy.EventsStrategy(this) } validate() { const result = { isValid: true, brokenRules: [], validators: [], status: STATUS.valid, complete: null }; this._unsubscribeFromAllChangeEvents(); this._pendingValidators = []; this._resetValidationInfo(); (0, _iterator.each)(this.validators, ((_, validator) => { const validatorResult = validator.validate(); result.isValid = result.isValid && validatorResult.isValid; if (validatorResult.brokenRules) { result.brokenRules = result.brokenRules.concat(validatorResult.brokenRules) } result.validators.push(validator); if (validatorResult.status === STATUS.pending) { this._addPendingValidator(validator) } this._subscribeToChangeEvents(validator) })); if (this._pendingValidators.length) { result.status = STATUS.pending } else { result.status = result.isValid ? STATUS.valid : STATUS.invalid; this._unsubscribeFromAllChangeEvents(); this._raiseValidatedEvent(result) } this._updateValidationInfo(result); return (0, _extend.extend)({}, this._validationInfo.result) } _subscribeToChangeEvents(validator) { validator.on("validating", this._onValidatorStatusChanged); validator.on("validated", this._onValidatorStatusChanged) } _unsubscribeFromChangeEvents(validator) { validator.off("validating", this._onValidatorStatusChanged); validator.off("validated", this._onValidatorStatusChanged) } _unsubscribeFromAllChangeEvents() { (0, _iterator.each)(this.validators, ((_, validator) => { this._unsubscribeFromChangeEvents(validator) })) } _updateValidationInfo(result) { this._validationInfo.result = result; if (result.status !== STATUS.pending) { return } if (!this._validationInfo.deferred) { this._validationInfo.deferred = (0, _deferred.Deferred)(); this._validationInfo.result.complete = this._validationInfo.deferred.promise() } } _addPendingValidator(validator) { const foundValidator = (0, _common.grep)(this._pendingValidators, (val => val === validator))[0]; if (!foundValidator) { this._pendingValidators.push(validator) } } _removePendingValidator(validator) { const index = this._pendingValidators.indexOf(validator); if (index >= 0) { this._pendingValidators.splice(index, 1) } } _orderBrokenRules(brokenRules) { let orderedRules = []; (0, _iterator.each)(this.validators, ((_, validator) => { const foundRules = (0, _common.grep)(brokenRules, (rule => rule.validator === validator)); if (foundRules.length) { orderedRules = orderedRules.concat(foundRules) } })); return orderedRules } _updateBrokenRules(result) { if (!this._validationInfo.result) { return } let { brokenRules: brokenRules } = this._validationInfo.result; const rules = (0, _common.grep)(brokenRules, (rule => rule.validator !== result.validator)); if (result.brokenRules) { brokenRules = rules.concat(result.brokenRules) } this._validationInfo.result.brokenRules = this._orderBrokenRules(brokenRules) } _onValidatorStatusChanged(result) { if (result.status === STATUS.pending) { this._addPendingValidator(result.validator); return } this._resolveIfComplete(result) } _resolveIfComplete(result) { this._removePendingValidator(result.validator); this._updateBrokenRules(result); if (!this._pendingValidators.length) { this._unsubscribeFromAllChangeEvents(); if (!this._validationInfo.result) { return } this._validationInfo.result.status = 0 === this._validationInfo.result.brokenRules.length ? STATUS.valid : STATUS.invalid; this._validationInfo.result.isValid = this._validationInfo.result.status === STATUS.valid; const res = (0, _extend.extend)({}, this._validationInfo.result, { complete: null }); const { deferred: deferred } = this._validationInfo; this._validationInfo.deferred = null; this._raiseValidatedEvent(res); deferred && setTimeout((() => { deferred.resolve(res) })) } } _raiseValidatedEvent(result) { this._eventsStrategy.fireEvent("validated", [result]) } _resetValidationInfo() { this._validationInfo = { result: null, deferred: null } } _synchronizeValidationInfo() { if (this._validationInfo.result) { this._validationInfo.result.validators = this.validators } } removeRegisteredValidator(validator) { const index = this.validators.indexOf(validator); if (index > -1) { this.validators.splice(index, 1); this._synchronizeValidationInfo(); this._resolveIfComplete({ validator: validator }) } } registerValidator(validator) { if (!this.validators.includes(validator)) { this.validators.push(validator); this._synchronizeValidationInfo() } } reset() { (0, _iterator.each)(this.validators, ((_, validator) => { validator.reset() })); this._pendingValidators = []; this._resetValidationInfo() } on(eventName, eventHandler) { this._eventsStrategy.on(eventName, eventHandler); return this } off(eventName, eventHandler) { this._eventsStrategy.off(eventName, eventHandler); return this } } const ValidationEngine = { groups: [], getGroupConfig(group) { const result = (0, _common.grep)(this.groups, (config => config.group === group)); if (result.length) { return result[0] } }, findGroup($element, model) { var _$element$data; const hasValidationGroup = null === (_$element$data = $element.data()) || void 0 === _$element$data || null === (_$element$data = _$element$data.dxComponents) || void 0 === _$element$data ? void 0 : _$element$data.includes("dxValidationGroup"); const validationGroup = hasValidationGroup && $element.dxValidationGroup("instance"); if (validationGroup) { return validationGroup } const $dxGroup = $element.parents(".dx-validationgroup").first(); if ($dxGroup.length) { return $dxGroup.dxValidationGroup("instance") } return model }, initGroups() { this.groups = []; this.addGroup(void 0, false) }, addGroup(group) { let isRemovable = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : true; let config = this.getGroupConfig(group); if (!config) { config = new GroupConfig(group, isRemovable); this.groups.push(config) } return config }, removeGroup(group) { const config = this.getGroupConfig(group); const index = this.groups.indexOf(config); if (index > -1) { this.groups.splice(index, 1) } return config }, _setDefaultMessage(info) { const { rule: rule, validator: validator, name: name } = info; if (!(0, _type.isDefined)(rule.message)) { if (validator.defaultFormattedMessage && (0, _type.isDefined)(name)) { rule.message = validator.defaultFormattedMessage(name) } else { rule.message = validator.defaultMessage() } } }, _addBrokenRule(info) { const { result: result, rule: rule } = info; if (!result.brokenRule) { result.brokenRule = rule } if (!result.brokenRules) { result.brokenRules = [] } result.brokenRules.push(rule) }, validate(value, rules, name) { var _rules$; let result = { name: name, value: value, brokenRule: null, brokenRules: null, isValid: true, validationRules: rules, pendingRules: null, status: STATUS.valid, complete: null }; const validator = null === rules || void 0 === rules || null === (_rules$ = rules[0]) || void 0 === _rules$ ? void 0 : _rules$.validator; const asyncRuleItems = []; (0, _iterator.each)(rules || [], ((_, rule) => { const ruleValidator = rulesValidators[rule.type]; let ruleValidationResult; if (ruleValidator) { if ((0, _type.isDefined)(rule.isValid) && rule.value === value && !rule.reevaluate) { if (!rule.isValid) { result.isValid = false; this._addBrokenRule({ result: result, rule: rule }); return false } return true } rule.value = value; if ("async" === rule.type) { asyncRuleItems.push({ rule: rule, ruleValidator: ruleValidator }); return true } ruleValidationResult = ruleValidator.validate(value, rule); rule.isValid = ruleValidationResult; if (!ruleValidationResult) { result.isValid = false; this._setDefaultMessage({ rule: rule, validator: ruleValidator, name: name }); this._addBrokenRule({ result: result, rule: rule }) } if (!rule.isValid) { return false } } else { throw _errors.default.Error("E0100") } })); if (result.isValid && !result.brokenRules && asyncRuleItems.length) { result = this._validateAsyncRules({ value: value, items: asyncRuleItems, result: result, name: name }) } this._synchronizeGroupValidationInfo(validator, result); result.status = result.pendingRules ? STATUS.pending : result.isValid ? STATUS.valid : STATUS.invalid; return result }, _synchronizeGroupValidationInfo(validator, result) { if (!validator) { return } const groupConfig = ValidationEngine.getGroupConfig(validator._validationGroup); groupConfig._updateBrokenRules.call(groupConfig, { validator: validator, brokenRules: result.brokenRules ?? [] }) }, _validateAsyncRules(_ref) { let { result: result, value: value, items: items, name: name } = _ref; const asyncResults = []; (0, _iterator.each)(items, ((_, item) => { const validateResult = item.ruleValidator.validate(value, item.rule); if (!(0, _type.isPromise)(validateResult)) { this._updateRuleConfig({ rule: item.rule, ruleResult: this._getPatchedRuleResult(validateResult), validator: item.ruleValidator, name: name }) } else { if (!result.pendingRules) { result.pendingRules = [] } result.pendingRules.push(item.rule); const asyncResult = validateResult.then((res => { const ruleResult = this._getPatchedRuleResult(res); this._updateRuleConfig({ rule: item.rule, ruleResult: ruleResult, validator: item.ruleValidator, name: name }); return ruleResult })); asyncResults.push(asyncResult) } })); if (asyncResults.length) { result.complete = Promise.all(asyncResults).then((values => this._getAsyncRulesResult({ result: result, values: values }))) } return result }, _updateRuleConfig(_ref2) { let { rule: rule, ruleResult: ruleResult, validator: validator, name: name } = _ref2; rule.isValid = ruleResult.isValid; if (!ruleResult.isValid) { if ((0, _type.isDefined)(ruleResult.message) && (0, _type.isString)(ruleResult.message) && ruleResult.message.length) { rule.message = ruleResult.message } else { this._setDefaultMessage({ rule: rule, validator: validator, name: name }) } } }, _getPatchedRuleResult(ruleResult) { let result; if ((0, _type.isObject)(ruleResult)) { result = (0, _extend.extend)({}, ruleResult); if (!(0, _type.isDefined)(result.isValid)) { result.isValid = true } } else { result = { isValid: (0, _type.isBoolean)(ruleResult) ? ruleResult : true } } return result }, _getAsyncRulesResult(_ref3) { let { values: values, result: result } = _ref3; (0, _iterator.each)(values, ((index, val) => { if (false === val.isValid) { result.isValid = val.isValid; const rule = result.pendingRules[index]; this._addBrokenRule({ result: result, rule: rule }) } })); result.pendingRules = null; result.complete = null; result.status = result.isValid ? STATUS.valid : STATUS.invalid; return result }, registerValidatorInGroup(group, validator) { const groupConfig = ValidationEngine.addGroup(group); groupConfig.registerValidator.call(groupConfig, validator) }, removeRegisteredValidator(group, validator) { const config = ValidationEngine.getGroupConfig(group); if (config) { config.removeRegisteredValidator.call(config, validator); const validatorsInGroup = config.validators; const isRemovable = config._isRemovable; const shouldRemoveGroup = 0 === validatorsInGroup.length && isRemovable; if (shouldRemoveGroup) { this.removeGroup(group) } } }, initValidationOptions(options) { const initedOptions = {}; if (options) { const syncOptions = ["isValid", "validationStatus", "validationError", "validationErrors"]; syncOptions.forEach((prop => { if (prop in options) { (0, _extend.extend)(initedOptions, this.synchronizeValidationOptions({ name: prop, value: options[prop] }, options)) } })) } return initedOptions }, synchronizeValidationOptions(_ref4, options) { let { name: name, value: value } = _ref4; switch (name) { case "validationStatus": { const isValid = value === STATUS.valid || value === STATUS.pending; return options.isValid !== isValid ? { isValid: isValid } : {} } case "isValid": { const { validationStatus: validationStatus } = options; let newStatus = validationStatus; if (value && validationStatus === STATUS.invalid) { newStatus = STATUS.valid } else if (!value && validationStatus !== STATUS.invalid) { newStatus = STATUS.invalid } return newStatus !== validationStatus ? { validationStatus: newStatus } : {} } case "validationErrors": { const validationError = !(null !== value && void 0 !== value && value.length) ? null : value[0]; return options.validationError !== validationError ? { validationError: validationError } : {} } case "validationError": { const { validationErrors: validationErrors } = options; if (!value && validationErrors) { return { validationErrors: null } } if (value && !validationErrors) { return { validationErrors: [value] } } if (value && validationErrors && value !== validationErrors[0]) { validationErrors[0] = value; return { validationErrors: validationErrors.slice() } } } } return {} }, validateGroup(group) { const groupConfig = ValidationEngine.getGroupConfig(group); if (!groupConfig) { throw _errors.default.Error("E0110") } return groupConfig.validate() }, resetGroup(group) { const groupConfig = ValidationEngine.getGroupConfig(group); if (!groupConfig) { throw _errors.default.Error("E0110") } return groupConfig.reset() } }; ValidationEngine.initGroups(); var _default = exports.default = ValidationEngine;