UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

815 lines (814 loc) • 27.4 kB
/** * DevExtreme (esm/ui/validation_engine.js) * Version: 21.1.4 * Build date: Mon Jun 21 2021 * * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import Class from "../core/class"; import { extend } from "../core/utils/extend"; import { inArray } from "../core/utils/array"; import { each } from "../core/utils/iterator"; import { EventsStrategy } from "../core/events_strategy"; import errors from "../core/errors"; import { grep } from "../core/utils/common"; import { isDefined, isString, isDate, isBoolean, isObject, isFunction, isPromise, isNumeric } from "../core/utils/type"; import numberLocalization from "../localization/number"; import messageLocalization from "../localization/message"; import Promise from "../core/polyfills/promise"; import { fromPromise, Deferred } from "../core/utils/deferred"; var STATUS = { valid: "valid", invalid: "invalid", pending: "pending" }; class BaseRuleValidator { constructor() { this.NAME = "base" } defaultMessage(value) { return messageLocalization.getFormatter("validation-".concat(this.NAME))(value) } defaultFormattedMessage(value) { return messageLocalization.getFormatter("validation-".concat(this.NAME, "-formatted"))(value) } _isValueEmpty(value) { return !rulesValidators.required.validate(value, {}) } validate(value, rule) { var valueArray = Array.isArray(value) ? value : [value]; var 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 (!isDefined(value)) { return false } if (false === value) { return false } value = String(value); if (rule.trim || !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 && isString(value)) { return !isNaN(numberLocalization.parse(value)) } else { return isNumeric(value) } } } class RangeRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "range" } _validate(value, rule) { if (false !== rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } var validNumber = rulesValidators.numeric.validate(value, rule); var validValue = isDefined(value) && "" !== value; var number = validNumber ? parseFloat(value) : validValue && value.valueOf(); var min = rule.min; var max = rule.max; if (!(validNumber || isDate(value)) && !validValue) { return false } if (isDefined(min)) { if (isDefined(max)) { return number >= min && number <= max } return number >= min } else if (isDefined(max)) { return number <= max } else { throw errors.Error("E0101") } } } class StringLengthRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "stringLength" } _validate(value, rule) { var _value; value = String(null !== (_value = value) && void 0 !== _value ? _value : ""); if (rule.trim || !isDefined(rule.trim)) { value = value.trim() } if (rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } return rulesValidators.range.validate(value.length, extend({}, rule)) } } class CustomRuleValidator extends BaseRuleValidator { constructor() { super(); this.NAME = "custom" } validate(value, rule) { if (rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } var validator = rule.validator; var dataGetter = validator && isFunction(validator.option) && validator.option("dataGetter"); var extraParams = isFunction(dataGetter) && dataGetter(); var params = { value: value, validator: validator, rule: rule }; if (extraParams) { extend(params, extraParams) } return rule.validationCallback(params) } } class AsyncRuleValidator extends CustomRuleValidator { constructor() { super(); this.NAME = "async" } validate(value, rule) { if (!isDefined(rule.reevaluate)) { extend(rule, { reevaluate: true }) } if (rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } var validator = rule.validator; var dataGetter = validator && isFunction(validator.option) && validator.option("dataGetter"); var extraParams = isFunction(dataGetter) && dataGetter(); var params = { value: value, validator: validator, rule: rule }; if (extraParams) { extend(params, extraParams) } var callbackResult = rule.validationCallback(params); if (!isPromise(callbackResult)) { throw errors.Error("E0103") } return this._getWrappedPromise(fromPromise(callbackResult).promise()) } _getWrappedPromise(promise) { var deferred = new Deferred; promise.then((function(res) { deferred.resolve(res) }), (function(err) { var res = { isValid: false }; if (isDefined(err)) { if (isString(err)) { res.message = err } else if (isObject(err) && isDefined(err.message) && 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.Error("E0102") } if (rule.ignoreEmptyValue && this._isValueEmpty(value)) { return true } extend(rule, { reevaluate: true }); var otherValue = rule.comparisonTarget(); var 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 } var pattern = rule.pattern; if (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, extend({}, rule, { pattern: /^[\d\w._-]+@[\d\w._-]+\.[\w]+$/i })) } } var 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 }; var GroupConfig = Class.inherit({ ctor(group) { this.group = group; this.validators = []; this._pendingValidators = []; this._onValidatorStatusChanged = this._onValidatorStatusChanged.bind(this); this._resetValidationInfo(); this._eventsStrategy = new EventsStrategy(this) }, validate() { var result = { isValid: true, brokenRules: [], validators: [], status: STATUS.valid, complete: null }; this._unsubscribeFromAllChangeEvents(); this._pendingValidators = []; this._resetValidationInfo(); each(this.validators, (_, validator) => { var 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 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() { 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 = new Deferred; this._validationInfo.result.complete = this._validationInfo.deferred.promise() } }, _addPendingValidator(validator) { var foundValidator = grep(this._pendingValidators, (function(val) { return val === validator }))[0]; if (!foundValidator) { this._pendingValidators.push(validator) } }, _removePendingValidator(validator) { var index = inArray(validator, this._pendingValidators); if (index >= 0) { this._pendingValidators.splice(index, 1) } }, _orderBrokenRules(brokenRules) { var orderedRules = []; each(this.validators, (function(_, validator) { var foundRules = grep(brokenRules, (function(rule) { return rule.validator === validator })); if (foundRules.length) { orderedRules = orderedRules.concat(foundRules) } })); return orderedRules }, _updateBrokenRules(result) { if (!this._validationInfo.result) { return } var brokenRules = this._validationInfo.result.brokenRules; var rules = grep(brokenRules, (function(rule) { return 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; var res = extend({}, this._validationInfo.result, { complete: null }); var deferred = this._validationInfo.deferred; this._resetValidationInfo(); 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) { var index = inArray(validator, this.validators); if (index > -1) { this.validators.splice(index, 1); this._synchronizeValidationInfo(); this._resolveIfComplete({ validator: validator }) } }, registerValidator(validator) { if (inArray(validator, this.validators) < 0) { this.validators.push(validator); this._synchronizeValidationInfo() } }, reset() { each(this.validators, (function(_, 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 } }); var ValidationEngine = { groups: [], getGroupConfig(group) { var result = grep(this.groups, (function(config) { return config.group === group })); if (result.length) { return result[0] } }, findGroup($element, model) { var $dxGroup = $element.parents(".dx-validationgroup").first(); if ($dxGroup.length) { return $dxGroup.dxValidationGroup("instance") } return model }, initGroups() { this.groups = []; this.addGroup() }, addGroup(group) { var config = this.getGroupConfig(group); if (!config) { config = new GroupConfig(group); this.groups.push(config) } return config }, removeGroup(group) { var config = this.getGroupConfig(group); var index = inArray(config, this.groups); if (index > -1) { this.groups.splice(index, 1) } return config }, _setDefaultMessage(info) { var { rule: rule, validator: validator, name: name } = info; if (!isDefined(rule.message)) { if (validator.defaultFormattedMessage && isDefined(name)) { rule.message = validator.defaultFormattedMessage(name) } else { rule.message = validator.defaultMessage() } } }, _addBrokenRule(info) { var { 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 result = { name: name, value: value, brokenRule: null, brokenRules: null, isValid: true, validationRules: rules, pendingRules: null, status: STATUS.valid, complete: null }; var asyncRuleItems = []; each(rules || [], (_, rule) => { var ruleValidator = rulesValidators[rule.type]; var ruleValidationResult; if (ruleValidator) { if (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.Error("E0100") } }); if (result.isValid && !result.brokenRules && asyncRuleItems.length) { result = this._validateAsyncRules({ value: value, items: asyncRuleItems, result: result, name: name }) } result.status = result.pendingRules ? STATUS.pending : result.isValid ? STATUS.valid : STATUS.invalid; return result }, _validateAsyncRules(_ref) { var { result: result, value: value, items: items, name: name } = _ref; var asyncResults = []; each(items, (_, item) => { var validateResult = item.ruleValidator.validate(value, item.rule); if (!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); var asyncResult = validateResult.then(res => { var 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) { var { rule: rule, ruleResult: ruleResult, validator: validator, name: name } = _ref2; rule.isValid = ruleResult.isValid; if (!ruleResult.isValid) { if (isDefined(ruleResult.message) && isString(ruleResult.message) && ruleResult.message.length) { rule.message = ruleResult.message } else { this._setDefaultMessage({ rule: rule, validator: validator, name: name }) } } }, _getPatchedRuleResult(ruleResult) { var result; if (isObject(ruleResult)) { result = extend({}, ruleResult); if (!isDefined(result.isValid)) { result.isValid = true } } else { result = { isValid: isBoolean(ruleResult) ? ruleResult : true } } return result }, _getAsyncRulesResult(_ref3) { var { values: values, result: result } = _ref3; each(values, (index, val) => { if (false === val.isValid) { result.isValid = val.isValid; var 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) { var groupConfig = ValidationEngine.addGroup(group); groupConfig.registerValidator.call(groupConfig, validator) }, _shouldRemoveGroup(group, validatorsInGroup) { var isDefaultGroup = void 0 === group; var isValidationGroupInstance = group && "dxValidationGroup" === group.NAME; return !isDefaultGroup && !isValidationGroupInstance && !validatorsInGroup.length }, removeRegisteredValidator(group, validator) { var config = ValidationEngine.getGroupConfig(group); if (config) { config.removeRegisteredValidator.call(config, validator); var validatorsInGroup = config.validators; if (this._shouldRemoveGroup(group, validatorsInGroup)) { this.removeGroup(group) } } }, initValidationOptions(options) { var initedOptions = {}; if (options) { ["isValid", "validationStatus", "validationError", "validationErrors"].forEach(prop => { if (prop in options) { extend(initedOptions, this.synchronizeValidationOptions({ name: prop, value: options[prop] }, options)) } }) } return initedOptions }, synchronizeValidationOptions(_ref4, options) { var { name: name, value: value } = _ref4; switch (name) { case "validationStatus": var isValid = value === STATUS.valid || value === STATUS.pending; return options.isValid !== isValid ? { isValid: isValid } : {}; case "isValid": var { validationStatus: validationStatus } = options; var 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": var validationError = !value || !value.length ? null : value[0]; return options.validationError !== validationError ? { validationError: validationError } : {}; case "validationError": var { validationErrors: validationErrors } = options; if (!value && validationErrors) { return { validationErrors: null } } else if (value && !validationErrors) { return { validationErrors: [value] } } else if (value && validationErrors && value !== validationErrors[0]) { validationErrors[0] = value; return { validationErrors: validationErrors.slice() } } } return {} }, validateGroup(group) { var groupConfig = ValidationEngine.getGroupConfig(group); if (!groupConfig) { throw errors.Error("E0110") } return groupConfig.validate() }, resetGroup(group) { var groupConfig = ValidationEngine.getGroupConfig(group); if (!groupConfig) { throw errors.Error("E0110") } return groupConfig.reset() } }; ValidationEngine.initGroups(); export default ValidationEngine;