@alifd/field
Version:
Fields can be used to manage data when it comes to form data manipulation and validation. After being associated with a component, the form data can be automatically written back, read, and verified.
1,235 lines (1,234 loc) • 50.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var validate_1 = tslib_1.__importDefault(require("@alifd/validate"));
var utils_1 = require("./utils");
var initMeta = {
state: '',
valueName: 'value',
trigger: 'onChange',
inputValues: [],
};
var Field = /** @class */ (function () {
function Field(com, options) {
if (options === void 0) { options = {}; }
var _this_1 = this;
if (!com) {
(0, utils_1.warning)('`this` is missing in `Field`, you should use like `new Field(this)`');
}
this.com = com;
this.fieldsMeta = {};
this.cachedBind = {};
this.instance = {};
this.instanceCount = {};
this.reRenders = {};
this.listeners = {};
// holds constructor values. Used for setting field defaults on init if no other value or initValue is passed.
// Also used caching values when using `parseName: true` before a field is initialized
this.values = Object.assign({}, options.values);
this.processErrorMessage = options.processErrorMessage;
this.afterValidateRerender = options.afterValidateRerender;
this.options = Object.assign({
parseName: false,
forceUpdate: false,
first: false,
onChange: function () { },
autoUnmount: true,
autoValidate: true,
}, options);
[
'init',
'getValue',
'getValues',
'setValue',
'setValues',
'getError',
'getErrors',
'setError',
'setErrors',
'validateCallback',
'validatePromise',
'getState',
'reset',
'resetToDefault',
'remove',
'spliceArray',
'addArrayValue',
'deleteArrayValue',
'getNames',
].forEach(function (m) {
_this_1[m] = _this_1[m].bind(_this_1);
});
}
Field.create = function (com, options) {
if (options === void 0) { options = {}; }
return new this(com, options);
};
Field.getUseField = function (_a) {
var _this_1 = this;
var useState = _a.useState, useMemo = _a.useMemo;
return function (options) {
if (options === void 0) { options = {}; }
var _a = tslib_1.__read(useState(), 2), setState = _a[1];
var field = useMemo(function () { return _this_1.create({ setState: setState }, options); }, [setState]);
return field;
};
};
/**
* 设置配置信息
* @param options - 配置
*/
Field.prototype.setOptions = function (options) {
Object.assign(this.options, options);
};
/**
* 初始化一个字段项
* @param name - 字段 key
* @param option - 字段配置
* @param rprops - 其它参数
*/
Field.prototype.init = function (name, option, rprops) {
var _a;
var _this_1 = this;
if (option === void 0) { option = {}; }
var id = option.id, initValue = option.initValue, _b = option.valueName, valueName = _b === void 0 ? 'value' : _b, _c = option.trigger, trigger = _c === void 0 ? 'onChange' : _c, _d = option.rules, rules = _d === void 0 ? [] : _d, _e = option.props, props = _e === void 0 ? {} : _e, _f = option.getValueFromEvent, getValueFromEvent = _f === void 0 ? null : _f, _g = option.getValueFormatter, getValueFormatter = _g === void 0 ? getValueFromEvent : _g, setValueFormatter = option.setValueFormatter, _h = option.autoValidate, autoValidate = _h === void 0 ? true : _h, reRender = option.reRender;
var parseName = this.options.parseName;
if (getValueFromEvent) {
(0, utils_1.warning)('`getValueFromEvent` has been deprecated in `Field`, use `getValueFormatter` instead of it');
}
var originalProps = Object.assign({}, props, rprops);
var defaultValueName = "default".concat(valueName[0].toUpperCase()).concat(valueName.slice(1));
var defaultValue;
if (typeof initValue !== 'undefined') {
defaultValue = initValue;
}
else if (typeof originalProps[defaultValueName] !== 'undefined') {
// here use typeof, in case of defaultValue={0}
defaultValue = originalProps[defaultValueName];
}
// get field from this.fieldsMeta or new one
var field = this._getInitMeta(name);
Object.assign(field, {
valueName: valueName,
initValue: defaultValue,
disabled: 'disabled' in originalProps ? originalProps.disabled : false,
getValueFormatter: getValueFormatter,
setValueFormatter: setValueFormatter,
rules: (0, utils_1.cloneToRuleArr)(rules),
ref: originalProps.ref,
});
var oldValue = field.value;
// Controlled Component, should always equal props.value
if (valueName in originalProps) {
var originalValue = originalProps[valueName];
// When rerendering set the values from props.value
if (parseName) {
// when parseName is true, field should not store value locally. To prevent sync issues
if (!('value' in field)) {
this._proxyFieldValue(field);
}
}
else {
this.values[name] = originalValue;
}
field.value = originalValue;
}
/**
* first init field (value not in field)
* should get field.value from this.values or defaultValue
*/
if (!('value' in field)) {
if (parseName) {
var cachedValue = (0, utils_1.getIn)(this.values, name);
if (typeof cachedValue !== 'undefined') {
oldValue = cachedValue;
}
var initValue_1 = typeof cachedValue !== 'undefined' ? cachedValue : defaultValue;
// when parseName is true, field should not store value locally. To prevent sync issues
this._proxyFieldValue(field);
field.value = initValue_1;
}
else {
var cachedValue = this.values[name];
if (typeof cachedValue !== 'undefined') {
field.value = cachedValue;
oldValue = cachedValue;
}
else if (typeof defaultValue !== 'undefined') {
// should be same with parseName, but compatible with old versions
field.value = defaultValue;
this.values[name] = field.value;
}
}
}
// field value init end
var newValue = field.value;
this._triggerFieldChange(name, newValue, oldValue, 'init');
// Component props
var inputProps = (_a = {
'data-meta': 'Field',
id: id || name,
ref: this._getCacheBind(name, "".concat(name, "__ref"), this._saveRef)
},
_a[valueName] = setValueFormatter
? setValueFormatter(field.value, field.inputValues)
: field.value,
_a);
var rulesMap = {};
if (this.options.autoValidate && autoValidate !== false) {
// trigger map in rules,
rulesMap = (0, utils_1.mapValidateRules)(field.rules, trigger);
var _loop_1 = function (action) {
// skip default trigger, which will trigger in step2
if (action === trigger) {
return "continue";
}
var actionRule = rulesMap[action];
inputProps[action] = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
_this_1._callNativePropsEvent.apply(_this_1, tslib_1.__spreadArray([action, originalProps], tslib_1.__read(args), false));
_this_1._validate(name, actionRule, action);
};
};
// step1 : validate hooks
for (var action in rulesMap) {
_loop_1(action);
}
}
// step2: onChange(trigger=onChange by default) hack
inputProps[trigger] = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var oldValue = _this_1.getValue(name);
_this_1._updateFieldValue.apply(_this_1, tslib_1.__spreadArray([name], tslib_1.__read(args), false));
var newValue = _this_1.getValue(name);
_this_1._triggerFieldChange(name, newValue, oldValue, 'change');
// clear validate error
_this_1._resetError(name);
_this_1._callNativePropsEvent.apply(_this_1, tslib_1.__spreadArray([trigger, originalProps], tslib_1.__read(args), false));
// call global onChange
_this_1.options.onChange(name, field.value);
// validate while onChange
var rule = rulesMap[trigger];
rule && _this_1._validate(name, rule, trigger);
_this_1._reRender(name, trigger);
};
// step3: save reRender function
if (reRender && typeof reRender === 'function') {
this.reRenders[name] = reRender;
}
delete originalProps[defaultValueName];
return Object.assign({}, originalProps, inputProps);
};
/**
* 获取单个输入控件的值
* @param name - 字段名
* @returns 字段值
*/
Field.prototype.getValue = function (name) {
if (this.options.parseName) {
return (0, utils_1.getIn)(this.values, name);
}
return this.values[name];
};
/**
* 获取一组输入控件的值
* @param names - 字段名数组
* @returns 不传入`names`参数,则获取全部字段的值
*/
Field.prototype.getValues = function (names) {
var _this_1 = this;
var allValues = {};
if (names && names.length) {
names.forEach(function (name) {
allValues[name] = _this_1.getValue(name);
});
}
else {
Object.assign(allValues, this.values);
}
return allValues;
};
/**
* 设置单个输入控件的值(默认会触发 render,请遵循 react 时机使用)
* @param name - 字段名
* @param value - 字段值
* @param reRender - 设置完成后是否重新渲染,默认为 true
* @param triggerChange - 是否触发 watch change,默认为 true
*/
Field.prototype.setValue = function (name, value, reRender, triggerChange) {
if (reRender === void 0) { reRender = true; }
if (triggerChange === void 0) { triggerChange = true; }
var oldValue = this.getValue(name);
if (name in this.fieldsMeta) {
this.fieldsMeta[name].value = value;
}
if (this.options.parseName) {
this.values = (0, utils_1.setIn)(this.values, name, value);
}
else {
this.values[name] = value;
}
var newValue = this.getValue(name);
if (triggerChange) {
this._triggerFieldChange(name, newValue, oldValue, 'setValue');
}
reRender && this._reRender(name, 'setValue');
};
/**
* 设置一组输入控件的值(默认会触发 render,请遵循 react 时机使用)
* @param fieldsValue - 一组输入控件值对象
* @param reRender - 设置完成后是否重新渲染,默认为 true
*/
Field.prototype.setValues = function (fieldsValue, reRender) {
var e_1, _a;
var _this_1 = this;
if (fieldsValue === void 0) { fieldsValue = {}; }
if (reRender === void 0) { reRender = true; }
if (!this.options.parseName) {
Object.keys(fieldsValue).forEach(function (name) {
_this_1.setValue(name, fieldsValue[name], false, true);
});
}
else {
// NOTE: this is a shallow merge
// Ex. we have two values a.b.c=1 ; a.b.d=2, and use setValues({a:{b:{c:3}}}) , then because of shallow merge a.b.d will be lost, we will get only {a:{b:{c:3}}}
// fieldsMeta[name].value is proxy from this.values[name] when parseName is true, so there is no need to assign value to fieldMeta
// shallow merge
var newValues_1 = Object.assign({}, this.values, fieldsValue);
var fields = this.getNames();
var allOldFieldValues = this.getValues(fields);
// record all old field values, exclude items overwritten by fieldsValue
var oldFieldValues = fields
.filter(function (name) { return !(0, utils_1.isOverwritten)(fieldsValue, name); })
.map(function (name) { return ({ name: name, value: _this_1.fieldsMeta[name].value }); });
// assign lost field value to newValues
oldFieldValues.forEach(function (_a) {
var name = _a.name, value = _a.value;
if (!(0, utils_1.hasIn)(newValues_1, name)) {
newValues_1 = (0, utils_1.setIn)(newValues_1, name, value);
}
});
// store the new values
this.values = newValues_1;
try {
// trigger changes after update
for (var fields_1 = tslib_1.__values(fields), fields_1_1 = fields_1.next(); !fields_1_1.done; fields_1_1 = fields_1.next()) {
var name_1 = fields_1_1.value;
this._triggerFieldChange(name_1, this.getValue(name_1), allOldFieldValues[name_1], 'setValue');
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (fields_1_1 && !fields_1_1.done && (_a = fields_1.return)) _a.call(fields_1);
}
finally { if (e_1) throw e_1.error; }
}
}
reRender && this._reRender();
};
/**
* 获取单个输入控件的 Error
* @param name - 字段名
* @returns 该字段的 Error
*/
Field.prototype.getError = function (name) {
var field = this._get(name);
if (field && field.errors && field.errors.length) {
return field.errors;
}
return null;
};
/**
* 获取一组输入控件的 Error
* @param names - 字段名列表
* @returns 不传入`names`参数,则获取全部字段的 Error
*/
Field.prototype.getErrors = function (names) {
var _this_1 = this;
var fields = names || this.getNames();
var allErrors = {};
fields.forEach(function (f) {
allErrors[f] = _this_1.getError(f);
});
return allErrors;
};
/**
* 设置单个输入控件的 Error
* @param name - 字段名
* @param errors - 错误信息
*/
Field.prototype.setError = function (name, errors) {
var err = Array.isArray(errors) ? errors : errors ? [errors] : [];
if (name in this.fieldsMeta) {
this.fieldsMeta[name].errors = err;
}
else {
this.fieldsMeta[name] = {
errors: err,
name: name,
};
}
if (this.fieldsMeta[name].errors && this.fieldsMeta[name].errors.length > 0) {
this.fieldsMeta[name].state = 'error';
}
else {
this.fieldsMeta[name].state = '';
}
this._reRender(name, 'setError');
};
/**
* 设置一组输入控件的 Error
*/
Field.prototype.setErrors = function (fieldsErrors) {
var _this_1 = this;
if (fieldsErrors === void 0) { fieldsErrors = {}; }
Object.keys(fieldsErrors).forEach(function (name) {
_this_1.setError(name, fieldsErrors[name]);
});
};
/**
* 获取单个字段的校验状态
* @param name - 字段名
*/
Field.prototype.getState = function (name) {
var field = this._get(name);
if (field && field.state) {
return field.state;
}
return '';
};
/**
* 校验 - Callback version
*/
Field.prototype.validateCallback = function (ns, cb) {
var _this_1 = this;
var _a = (0, utils_1.getParams)(ns, cb), names = _a.names, callback = _a.callback;
var fieldNames = names || this.getNames();
var descriptor = {};
var values = {};
var hasRule = false;
for (var i = 0; i < fieldNames.length; i++) {
var name_2 = fieldNames[i];
var field = this._get(name_2);
if (!field) {
continue;
}
if (field.rules && field.rules.length) {
descriptor[name_2] = field.rules;
values[name_2] = this.getValue(name_2);
hasRule = true;
// clear error
field.errors = [];
field.state = '';
}
}
if (!hasRule) {
var errors = this.formatGetErrors(fieldNames);
callback && callback(errors, this.getValues(names ? fieldNames : []));
return;
}
var validate = new validate_1.default(descriptor, {
first: this.options.first,
messages: this.options.messages,
});
validate.validate(values, function (errors) {
var errorsGroup = null;
if (errors && errors.length) {
errorsGroup = {};
errors.forEach(function (e) {
var fieldName = e.field;
if (!errorsGroup[fieldName]) {
errorsGroup[fieldName] = {
errors: [],
};
}
var fieldErrors = errorsGroup[fieldName].errors;
fieldErrors.push(e.message);
});
}
if (errorsGroup) {
// update error in every Field
Object.keys(errorsGroup).forEach(function (i) {
var field = _this_1._get(i);
if (field) {
field.errors = (0, utils_1.getErrorStrs)(errorsGroup[i].errors, _this_1.processErrorMessage);
field.state = 'error';
}
});
}
var formattedGetErrors = _this_1.formatGetErrors(fieldNames);
if (formattedGetErrors) {
errorsGroup = Object.assign({}, formattedGetErrors, errorsGroup);
}
// update to success which has no error
for (var i = 0; i < fieldNames.length; i++) {
var name_3 = fieldNames[i];
var field = _this_1._get(name_3);
if (field && field.rules && !(errorsGroup && name_3 in errorsGroup)) {
field.state = 'success';
}
}
callback && callback(errorsGroup, _this_1.getValues(names ? fieldNames : []));
_this_1._reRender(names, 'validate');
_this_1._triggerAfterValidateRerender(errorsGroup);
});
};
/**
* 校验 - Promise version
*/
Field.prototype.validatePromise = function (ns, formatter) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _a, names, callback, fieldNames, descriptor, values, hasRule, i, name_4, field, errors_1, validate, results, errors, errorsGroup, callbackResults, error_1;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = (0, utils_1.getParams)(ns, formatter), names = _a.names, callback = _a.callback;
fieldNames = names || this.getNames();
descriptor = {};
values = {};
hasRule = false;
for (i = 0; i < fieldNames.length; i++) {
name_4 = fieldNames[i];
field = this._get(name_4);
if (!field) {
continue;
}
if (field.rules && field.rules.length) {
descriptor[name_4] = field.rules;
values[name_4] = this.getValue(name_4);
hasRule = true;
// clear error
field.errors = [];
field.state = '';
}
}
if (!hasRule) {
errors_1 = this.formatGetErrors(fieldNames);
if (callback) {
return [2 /*return*/, callback({
errors: errors_1,
values: this.getValues(names ? fieldNames : []),
})];
}
else {
return [2 /*return*/, {
errors: errors_1,
values: this.getValues(names ? fieldNames : []),
}];
}
}
validate = new validate_1.default(descriptor, {
first: this.options.first,
messages: this.options.messages,
});
return [4 /*yield*/, validate.validatePromise(values)];
case 1:
results = _b.sent();
errors = (results && results.errors) || [];
errorsGroup = this._getErrorsGroup({ errors: errors, fieldNames: fieldNames });
callbackResults = {
errors: errorsGroup,
values: this.getValues(names ? fieldNames : []),
};
_b.label = 2;
case 2:
_b.trys.push([2, 5, , 6]);
if (!callback) return [3 /*break*/, 4];
return [4 /*yield*/, callback(callbackResults)];
case 3:
callbackResults = _b.sent();
_b.label = 4;
case 4: return [3 /*break*/, 6];
case 5:
error_1 = _b.sent();
return [2 /*return*/, error_1];
case 6:
this._reRender(names, 'validate');
// afterValidateRerender 作为通用属性,在 callback 和 promise 两个版本的 validate 中保持相同行为
this._triggerAfterValidateRerender(errorsGroup);
return [2 /*return*/, callbackResults];
}
});
});
};
/**
* 重置一组输入控件的值,并清空校验信息
* @param names - 要重置的字段名,不传递则重置全部字段
*/
Field.prototype.reset = function (ns) {
this._reset(ns, false);
};
/**
* 重置一组输入控件的值为默认值,并清空校验信息
* @param names - 要重置的字段名,不传递则重置全部字段
*/
Field.prototype.resetToDefault = function (ns) {
this._reset(ns, true);
};
/**
* 获取所有字段名列表
*/
Field.prototype.getNames = function () {
var fieldsMeta = this.fieldsMeta;
return Object.keys(fieldsMeta).filter(function () {
return true;
});
};
/**
* 删除某一个或者一组控件的数据,删除后与之相关的 validate/value 都会被清空
* @param name - 要删除的字段名,不传递则删除全部字段
*/
Field.prototype.remove = function (ns) {
var _this_1 = this;
if (typeof ns === 'string') {
ns = [ns];
}
if (!ns) {
this.values = {};
}
var names = ns || Object.keys(this.fieldsMeta);
names.forEach(function (name) {
if (name in _this_1.fieldsMeta) {
delete _this_1.fieldsMeta[name];
}
if (_this_1.options.parseName) {
_this_1.values = (0, utils_1.deleteIn)(_this_1.values, name);
}
else {
delete _this_1.values[name];
}
});
};
/**
* 向指定数组字段内添加数据
* @param name - 字段名
* @param index - 开始添加的索引
* @param argv - 新增的数据
*/
Field.prototype.addArrayValue = function (name, index) {
var argv = [];
for (var _i = 2; _i < arguments.length; _i++) {
argv[_i - 2] = arguments[_i];
}
return this._spliceArrayValue.apply(this, tslib_1.__spreadArray([name, index, 0], tslib_1.__read(argv), false));
};
/**
* 删除指定字段数组内的数据
* @param name - 变量名
* @param index - 开始删除的索引
* @param howmany - 删除几个数据,默认为 1
*/
Field.prototype.deleteArrayValue = function (name, index, howmany) {
if (howmany === void 0) { howmany = 1; }
return this._spliceArrayValue(name, index, howmany);
};
/**
* splice in a Array [deprecated]
* @deprecated Use `addArrayValue` or `deleteArrayValue` instead
* @param keyMatch - like name.\{index\}
* @param startIndex - index
*/
Field.prototype.spliceArray = function (keyMatch, startIndex) {
var e_2, _a;
var _this_1 = this;
// @ts-expect-error FIXME 无效的 if 逻辑,恒定为 false
if (keyMatch.match(/{index}$/) === -1) {
(0, utils_1.warning)('key should match /{index}$/');
return;
}
// regex to match field names in the same target array
var reg = keyMatch.replace('{index}', '(\\d+)');
var keyReg = new RegExp("^".concat(reg));
var listMap = {};
/**
* keyMatch='key.\{index\}'
* case 1: names=['key.0', 'key.1'], should delete 'key.1'
* case 2: names=['key.0.name', 'key.0.email', 'key.1.name', 'key.1.email'], should delete 'key.1.name', 'key.1.email'
*/
var names = this.getNames();
var willChangeNames = [];
names.forEach(function (n) {
// is name in the target array?
var ret = keyReg.exec(n);
if (ret) {
var index = parseInt(ret[1]);
if (index > startIndex) {
var l = listMap[index];
var item = {
from: n,
to: "".concat(keyMatch.replace('{index}', (index - 1).toString())).concat(n.replace(ret[0], '')),
};
willChangeNames.push(item.from);
if (names.includes(item.to)) {
willChangeNames.push(item.to);
}
if (!l) {
listMap[index] = [item];
}
else {
l.push(item);
}
}
}
});
var oldValues = this.getValues(willChangeNames);
var idxList = Object.keys(listMap)
.map(function (i) {
return {
index: Number(i),
list: listMap[i],
};
})
// @ts-expect-error FIXME 返回 boolean 值并不能正确排序
.sort(function (a, b) { return a.index < b.index; });
// should be continuous array
if (idxList.length > 0 && idxList[0].index === startIndex + 1) {
idxList.forEach(function (l) {
var list = l.list;
list.forEach(function (i) {
var v = _this_1.getValue(i.from); // get index value
_this_1.setValue(i.to, v, false, false); // set value to index - 1
});
});
var lastIdxList = idxList[idxList.length - 1];
lastIdxList.list.forEach(function (i) {
_this_1.remove(i.from);
});
var parentName = keyMatch.replace('.{index}', '');
parentName = parentName.replace('[{index}]', '');
var parent_1 = this.getValue(parentName);
if (parent_1) {
// if parseName=true then parent is an Array object but does not know an element was removed
// this manually decrements the array length
parent_1.length--;
}
}
try {
for (var willChangeNames_1 = tslib_1.__values(willChangeNames), willChangeNames_1_1 = willChangeNames_1.next(); !willChangeNames_1_1.done; willChangeNames_1_1 = willChangeNames_1.next()) {
var name_5 = willChangeNames_1_1.value;
this._triggerFieldChange(name_5, this.getValue(name_5), oldValues[name_5], 'setValue');
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (willChangeNames_1_1 && !willChangeNames_1_1.done && (_a = willChangeNames_1.return)) _a.call(willChangeNames_1);
}
finally { if (e_2) throw e_2.error; }
}
};
Field.prototype.get = function (name) {
if (name) {
return this._get(name);
}
else {
return this.fieldsMeta;
}
};
/**
* 监听字段值变化
* @param names - 监听的 name 列表
* @param callback - 变化回调
* @returns 解除监听回调
*/
Field.prototype.watch = function (names, callback) {
var e_3, _a;
var _this_1 = this;
try {
for (var names_1 = tslib_1.__values(names), names_1_1 = names_1.next(); !names_1_1.done; names_1_1 = names_1.next()) {
var name_6 = names_1_1.value;
if (!this.listeners[name_6]) {
this.listeners[name_6] = new Set();
}
var set = this.listeners[name_6];
set.add(callback);
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (names_1_1 && !names_1_1.done && (_a = names_1.return)) _a.call(names_1);
}
finally { if (e_3) throw e_3.error; }
}
return function () {
var e_4, _a;
try {
for (var names_2 = tslib_1.__values(names), names_2_1 = names_2.next(); !names_2_1.done; names_2_1 = names_2.next()) {
var name_7 = names_2_1.value;
if (_this_1.listeners[name_7]) {
_this_1.listeners[name_7].delete(callback);
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (names_2_1 && !names_2_1.done && (_a = names_2.return)) _a.call(names_2);
}
finally { if (e_4) throw e_4.error; }
}
};
};
Field.prototype._get = function (name) {
return name in this.fieldsMeta ? this.fieldsMeta[name] : null;
};
Field.prototype._getInitMeta = function (name) {
if (!(name in this.fieldsMeta)) {
this.fieldsMeta[name] = Object.assign({ name: name }, initMeta);
}
return this.fieldsMeta[name];
};
Field.prototype._getErrorsGroup = function (_a) {
var _this_1 = this;
var errors = _a.errors, fieldNames = _a.fieldNames;
var errorsGroup = null;
if (errors && errors.length) {
errorsGroup = {};
errors.forEach(function (e) {
var fieldName = e.field;
if (!errorsGroup[fieldName]) {
errorsGroup[fieldName] = {
errors: [],
};
}
var fieldErrors = errorsGroup[fieldName].errors;
fieldErrors.push(e.message);
});
}
if (errorsGroup) {
// update error in every Field
Object.keys(errorsGroup).forEach(function (i) {
var field = _this_1._get(i);
if (field) {
field.errors = (0, utils_1.getErrorStrs)(errorsGroup[i].errors, _this_1.processErrorMessage);
field.state = 'error';
}
});
}
var formattedGetErrors = this.formatGetErrors(fieldNames);
if (formattedGetErrors) {
errorsGroup = Object.assign({}, formattedGetErrors, errorsGroup);
}
// update to success which has no error
for (var i = 0; i < fieldNames.length; i++) {
var name_8 = fieldNames[i];
var field = this._get(name_8);
if (field && field.rules && !(errorsGroup && name_8 in errorsGroup)) {
field.state = 'success';
}
}
return errorsGroup;
};
Field.prototype._reset = function (ns, backToDefault) {
var _this_1 = this;
if (typeof ns === 'string') {
ns = [ns];
}
var changed = false;
var names = ns || Object.keys(this.fieldsMeta);
var oldValues = this.getValues(names);
if (!ns) {
this.values = {};
}
names.forEach(function (name) {
var field = _this_1._get(name);
if (field) {
changed = true;
field.value = backToDefault ? field.initValue : undefined;
field.state = '';
delete field.errors;
delete field.rules;
delete field.rulesMap;
if (_this_1.options.parseName) {
_this_1.values = (0, utils_1.setIn)(_this_1.values, name, field.value);
}
else {
_this_1.values[name] = field.value;
}
}
_this_1._triggerFieldChange(name, _this_1.getValue(name), oldValues[name], 'reset');
});
if (changed) {
this._reRender(names, 'reset');
}
};
Field.prototype._resetError = function (name) {
var field = this._get(name);
if (field) {
delete field.errors; //清空错误
field.state = '';
}
};
Field.prototype._reRender = function (name, action) {
var _this_1 = this;
// 指定了字段列表且字段存在对应的自定义渲染函数
if (name) {
var names = Array.isArray(name) ? name : [name];
if (names.length && names.every(function (n) { return _this_1.reRenders[n]; })) {
names.forEach(function (n) {
var reRender = _this_1.reRenders[n];
reRender(action);
});
return;
}
}
if (this.com) {
if (!this.options.forceUpdate && this.com.setState) {
this.com.setState({});
}
else if (this.com.forceUpdate) {
this.com.forceUpdate(); //forceUpdate 对性能有较大的影响,成指数上升
}
}
};
/**
* Get errors using `getErrors` and format to match the structure of errors returned in field.validate
*/
Field.prototype.formatGetErrors = function (names) {
var errors = this.getErrors(names);
var formattedErrors = null;
for (var field in errors) {
if (errors.hasOwnProperty(field) && errors[field]) {
var errorsObj = errors[field];
if (!formattedErrors) {
formattedErrors = {};
}
formattedErrors[field] = { errors: errorsObj };
}
}
return formattedErrors;
};
/**
* call native event from props.onXx
* eg: props.onChange props.onBlur props.onFocus
*/
Field.prototype._callNativePropsEvent = function (action, props) {
var args = [];
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
action in props &&
typeof props[action] === 'function' && props[action].apply(props, tslib_1.__spreadArray([], tslib_1.__read(args), false));
};
Field.prototype._proxyFieldValue = function (field) {
var _this = this;
Object.defineProperty(field, 'value', {
configurable: true,
enumerable: true,
get: function () {
return (0, utils_1.getIn)(_this.values, this.name);
},
set: function (v) {
// 此处 this 解释同上
_this.values = (0, utils_1.setIn)(_this.values, this.name, v);
return true;
},
});
};
/**
* update field.value and validate
*/
Field.prototype._updateFieldValue = function (name) {
var others = [];
for (var _i = 1; _i < arguments.length; _i++) {
others[_i - 1] = arguments[_i];
}
var e = others[0];
var field = this._get(name);
if (!field) {
return;
}
field.value = field.getValueFormatter ? field.getValueFormatter.apply(this, others) : (0, utils_1.getValueFromEvent)(e);
field.inputValues = others;
if (this.options.parseName) {
this.values = (0, utils_1.setIn)(this.values, name, field.value);
}
else {
this.values[name] = field.value;
}
};
/**
* ref must always be the same function, or if not it will be triggerd every time.
*/
Field.prototype._getCacheBind = function (name, action, fn) {
var cache = (this.cachedBind[name] = this.cachedBind[name] || {});
if (!cache[action]) {
cache[action] = fn.bind(this, name);
}
return cache[action];
};
Field.prototype._setCache = function (name, action, hander) {
var cache = (this.cachedBind[name] = this.cachedBind[name] || {});
cache[action] = hander;
};
Field.prototype._getCache = function (name, action) {
var cache = this.cachedBind[name] || {};
return cache[action];
};
Field.prototype._saveRef = function (name, component) {
var key = "".concat(name, "_field");
var autoUnmount = this.options.autoUnmount;
if (!component && autoUnmount) {
// more than one component, do nothing
this.instanceCount[name] && this.instanceCount[name]--;
if (this.instanceCount[name] > 0) {
return;
}
// component with same name (eg: type ? <A name="n"/>:<B name="n"/>)
// while type changed, B will render before A unmount. so we should cached value for B
// step: render -> B mount -> 1. _saveRef(A, null) -> 2. _saveRef(B, ref) -> render
// 1. _saveRef(A, null)
var cache = this.fieldsMeta[name];
if (cache) {
if (this.options.parseName) {
// 若 parseName 模式下,因为 value 为 getter、setter,所以将当前值记录到_value 内
cache._value = cache.value;
}
this._setCache(name, key, cache);
}
// after destroy, delete data
delete this.instance[name];
delete this.reRenders[name];
var oldValue = this.getValue(name);
this.remove(name);
var newValue = this.getValue(name);
this._triggerFieldChange(name, newValue, oldValue, 'unmount');
return;
}
// 2. _saveRef(B, ref) (eg: same name but different compoent may be here)
if (autoUnmount && !this.fieldsMeta[name] && this._getCache(name, key)) {
var cache = this._getCache(name, key);
this.fieldsMeta[name] = cache;
// 若 parseName 模式,则使用_value 作为值设置到 values 内
this.setValue(name, this.options.parseName ? cache._value : cache.value, false, false);
this.options.parseName && '_value' in cache && delete cache._value;
}
// only one time here
var field = this._get(name);
if (field) {
//When the autoUnmount is false, the component uninstallation needs to clear the verification information to avoid blocking the validation.
if (!component && !autoUnmount) {
field.state = '';
delete field.errors;
delete field.rules;
delete field.rulesMap;
}
var ref = field.ref;
if (ref) {
if (typeof ref === 'string') {
throw new Error("can not set string ref for ".concat(name));
}
else if (typeof ref === 'function') {
ref(component);
}
else if (typeof ref === 'object' && 'current' in ref) {
// while ref = React.createRef() ref={ current: null}
ref.current = component;
}
}
// mount
if (autoUnmount && component) {
var cnt = this.instanceCount[name];
if (!cnt) {
cnt = 0;
}
this.instanceCount[name] = cnt + 1;
}
this.instance[name] = component;
}
};
Field.prototype._validate = function (name, rule, trigger) {
var _a, _b;
var _this_1 = this;
var field = this._get(name);
if (!field) {
return;
}
var value = field.value;
field.state = 'loading';
var validate = this._getCache(name, trigger);
if (validate && typeof validate.abort === 'function') {
validate.abort();
}
validate = new validate_1.default((_a = {}, _a[name] = rule, _a), { messages: this.options.messages });
this._setCache(name, trigger, validate);
validate.validate((_b = {},
_b[name] = value,
_b), function (errors) {
var newErrors, newState;
if (errors && errors.length) {
newErrors = (0, utils_1.getErrorStrs)(errors, _this_1.processErrorMessage);
newState = 'error';
}
else {
newErrors = [];
newState = 'success';
}
var reRender = false;
// only status or errors changed, Rerender
if (newState !== field.state ||
!field.errors ||
newErrors.length !== field.errors.length ||
newErrors.find(function (e, idx) { return e !== field.errors[idx]; })) {
reRender = true;
}
field.errors = newErrors;
field.state = newState;
reRender && _this_1._reRender(name, 'validate');
});
};
/**
* splice array
*/
Field.prototype._spliceArrayValue = function (key, index, howmany) {
var e_5, _a;
var _this_1 = this;
var argv = [];
for (var _i = 3; _i < arguments.length; _i++) {
argv[_i - 3] = arguments[_i];
}
var argc = argv.length;
var offset = howmany - argc; // how the reset fieldMeta move
var startIndex = index + howmany; // 计算起点
/**
* eg: call _spliceArrayValue('key', 1) to delete 'key.1':
* case 1: names=['key.0', 'key.1']; delete 'key.1';
* case 2: names=['key.0', 'key.1', 'key.2']; key.1= key.2; delete key.2;
* case 3: names=['key.0.name', 'key.0.email', 'key.1.name', 'key.1.email'], should delete 'key.1.name', 'key.1.email'
* eg: call _spliceArrayValue('key', 1, item) to add 'key.1':
* case 1: names=['key.0']; add 'key.1' = item;
* case 2: names=['key.0', 'key.1']; key.2= key.1; delete key.1; add key.1 = item;
*/
var listMap = {}; // eg: {1:[{from: 'key.2.name', to: 'key.1.name'}, {from: 'key.2.email', to: 'key.1.email'}]}
var replacedReg = /\$/g;
// 替换特殊字符$
var replacedKey = key.replace(replacedReg, '\\$&');
var keyReg = new RegExp("^(".concat(replacedKey, ".)(\\d+)"));
var replaceArgv = [];
var names = this.getNames();
var willChangeNames = [];
// logic of offset fix begin
names.forEach(function (n) {
var ret = keyReg.exec(n);
if (ret) {
var idx_1 = parseInt(ret[2]); // get index of 'key.0.name'
if (idx_1 >= startIndex) {
var l = listMap[idx_1];
var item = {
from: n,
to: n.replace(keyReg, function (match, p1) { return "".concat(p1).concat(idx_1 - offset); }),
};
willChangeNames.push(item.from);
if (names.includes(item.to)) {
willChangeNames.push(item.to);
}
if (!l) {
listMap[idx_1] = [item];
}
else {
l.push(item);
}
}
// in case of offsetList.length = 0, eg: delete last element
if (offset > 0 && idx_1 >= index && idx_1 < index + howmany) {
replaceArgv.push(n);
}
}
});
var oldValues = this.getValues(willChangeNames);
// sort with index eg: [{index:1, list: [{from: 'key.2.name', to: 'key.1.name'}]}, {index:2, list: [...]}]
var offsetList = Object.keys(listMap)
.map(function (i) {
return {
index: Number(i),
list: listMap[i],
};
})
.sort(function (a, b) { return (offset > 0 ? a.index - b.index : b.index - a.index); });
offsetList.forEach(function (l) {
var list = l.list;
list.forEach(function (i) {
_this_1.fieldsMeta[i.to] = _this_1.fieldsMeta[i.from];
// 移位后,同步调整 name
_this_1.fieldsMeta[i.to].name = i.to;
});
});
// delete copy data
if (offsetList.length > 0) {
var removeList = offsetList.slice(offsetList.length - (offset < 0 ? -offset : offset), offsetList.length);
removeList.forEach(function (item) {
item.list.forEach(function (i) {
delete _this_1.fieldsMeta[i.from];
});
});
}
else {
// will get from this.values while rerender
replaceArgv.forEach(function (i) {
delete _this_1.fieldsMeta[i];
});
}
var p = this.getValue(key);
if (p) {
p.splice.apply(p, tslib_1.__spreadArray([index, howmany], tslib_1.__read(argv), false));
}
try {
for (var willChangeNames_2 = tslib_1.__values(willChangeNames), willChangeNames_2_1 = willChangeNames_2.next(); !willChangeNames_2_1.done; willChangeNames_2_1 = willChangeNames_2.next()) {
var name_9 = willChangeNames_2_1.value;
this._triggerFieldChange(name_9, this.getValue(name_9), oldValues[name_9], 'setValue');
}
}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (willChangeNames_2_1 && !willChangeNames_2_1.done && (_a = willChangeNames_2.return)) _a.call(willChangeNames_2);
}
finally { if (e_5) throw e_5.error; }
}
this._reRender();
};
Field.prototype._triggerFieldChange = function (name, value, oldValue, triggerType) {
var e_6, _a;
// same value should not trigger change
if (Object.is(value, oldValue)) {
return;
}
var listenerSet = this.listeners[name];
if (!(listenerSet === null || listenerSet === void 0 ? void 0 : listenerSet.size)) {
return;
}
try {
for (var listenerSet_1 = tslib_1.__values(listenerSet), listenerSet_1_1 = listenerSet_1.next(); !listenerSet_1_1.done; listenerSet_1_1 = listenerSet_1.next()) {
var callback = listenerSet_1_1.value;
callback(name, value, oldValue, triggerType);
}
}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (listenerSet_1_1 && !listenerSet_1_1.done && (_a = listenerSet_1.return)) _a.call(listenerSet_1);
}
finally { if (e_6) throw e_6.error; }
}
};
Field.prototype._triggerAfterValidateRerender = function (errorsGroup) {
if (typeof this.afterValidateRerender === 'function') {
this.afterValidateRerender({
errorsGroup: errorsGroup,
options: this.options,
instance: this.instance,
});
}
};
return Field;
}());
tslib_1.__exportStar(require("./types"), exports);
exports.default = Field;