UNPKG

@alifd/next

Version:

A configurable component library for web built on React.

741 lines (588 loc) 23.6 kB
'use strict'; exports.__esModule = true; var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _util = require('../util'); var _validate2 = require('../validate'); var _validate3 = _interopRequireDefault(_validate2); var _utils = require('./utils'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var initMeta = { state: '', valueName: 'value', trigger: 'onChange' }; var Field = function () { function Field(com) { var _this = this; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; (0, _classCallCheck3.default)(this, Field); if (!com) { _util.log.warning('`this` is missing in `Field`, you should use like `new Field(this)`'); } this.com = com; this.fieldsMeta = {}; this.cachedBind = {}; this.instance = {}; this.initValues = options.values || {}; this.options = (0, _extends3.default)({ parseName: false, forceUpdate: false, scrollToFirstError: true, first: false, onChange: _util.func.noop, autoUnmount: true, autoValidate: true }, options); ['init', 'getValue', 'getValues', 'setValue', 'setValues', 'getError', 'getErrors', 'setError', 'setErrors', 'validate', 'getState', 'reset', 'resetToDefault', 'remove', 'spliceArray'].forEach(function (m) { _this[m] = _this[m].bind(_this); }); if (options.values) { this.setValues(options.values, false); } } Field.prototype.setOptions = function setOptions(options) { (0, _extends3.default)(this.options, options); }; /** * Controlled Component * @param {String} name * @param {Object} fieldOption * @returns {Object} {value, onChange} */ Field.prototype.init = function init(name) { var _inputProps, _this2 = this; var fieldOption = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var rprops = arguments[2]; var initValue = fieldOption.initValue, _fieldOption$valueNam = fieldOption.valueName, valueName = _fieldOption$valueNam === undefined ? 'value' : _fieldOption$valueNam, _fieldOption$trigger = fieldOption.trigger, trigger = _fieldOption$trigger === undefined ? 'onChange' : _fieldOption$trigger, _fieldOption$rules = fieldOption.rules, rules = _fieldOption$rules === undefined ? [] : _fieldOption$rules, _fieldOption$props = fieldOption.props, props = _fieldOption$props === undefined ? {} : _fieldOption$props, _fieldOption$getValue = fieldOption.getValueFromEvent, getValueFromEvent = _fieldOption$getValue === undefined ? null : _fieldOption$getValue, _fieldOption$autoVali = fieldOption.autoValidate, autoValidate = _fieldOption$autoVali === undefined ? true : _fieldOption$autoVali; var originalProps = (0, _extends3.default)({}, props, rprops); var defaultValueName = 'default' + valueName[0].toUpperCase() + valueName.slice(1); var field = this._getInitMeta(name); var defaultValue = void 0; if (typeof initValue !== 'undefined') { defaultValue = initValue; } else if (originalProps[defaultValueName]) { defaultValue = originalProps[defaultValueName]; } else { defaultValue = (0, _utils.getIn)(this.initValues, name); } (0, _extends3.default)(field, { valueName: valueName, initValue: defaultValue, disabled: 'disabled' in originalProps ? originalProps.disabled : false, getValueFromEvent: getValueFromEvent, rules: Array.isArray(rules) ? rules : [rules], ref: originalProps.ref }); // Controlled Component if (valueName in originalProps) { field.value = originalProps[valueName]; } if (!('value' in field)) { field.value = defaultValue; } // Component props var inputProps = (_inputProps = { 'data-meta': 'Field', id: name, ref: this._getCacheBind(name, name + '__ref', this._saveRef) }, _inputProps[valueName] = field.value, _inputProps); var rulesMap = {}; if (this.options.autoValidate && autoValidate !== false) { // trigger map rulesMap = (0, _utils.mapValidateRules)(field.rules, trigger); // validate hook var _loop = function _loop(action) { if (action === trigger) { return 'continue'; } var actionRule = rulesMap[action]; inputProps[action] = function () { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this2._validate(name, actionRule, action); _this2._callPropsEvent.apply(_this2, [action, originalProps].concat(args)); _this2._reRender(); }; }; for (var action in rulesMap) { var _ret = _loop(action); if (_ret === 'continue') continue; } } // onChange hack inputProps[trigger] = function () { for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } _this2._callOnChange.apply(_this2, [name, rulesMap[trigger], trigger].concat(args)); _this2._callPropsEvent.apply(_this2, [trigger, originalProps].concat(args)); _this2.options.onChange(name, field.value); _this2._reRender(); }; delete originalProps[defaultValueName]; return (0, _extends3.default)({}, originalProps, inputProps); }; /** * event on props * props.onChange props.onBlur */ Field.prototype._callPropsEvent = function _callPropsEvent(action, props) { for (var _len3 = arguments.length, args = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) { args[_key3 - 2] = arguments[_key3]; } action in props && typeof props[action] === 'function' && props[action].apply(props, args); }; Field.prototype._getInitMeta = function _getInitMeta(name) { if (!(name in this.fieldsMeta)) { this.fieldsMeta[name] = (0, _extends3.default)({}, initMeta); } return this.fieldsMeta[name]; }; /** * update field.value and validate */ Field.prototype._callOnChange = function _callOnChange(name, rule, trigger) { for (var _len4 = arguments.length, others = Array(_len4 > 3 ? _len4 - 3 : 0), _key4 = 3; _key4 < _len4; _key4++) { others[_key4 - 3] = arguments[_key4]; } var e = others[0]; var field = this._get(name); if (!field) { return; } field.value = field.getValueFromEvent ? field.getValueFromEvent.apply(this, others) : (0, _utils.getValueFromEvent)(e); this._resetError(name); // validate while onChange rule && this._validate(name, rule, trigger); }; /** * ref must always be the same function, or if not it will be triggerd every time. * @param {String} name name of component * @param {String} action key to find ref * @param {Function} fn saveRef */ Field.prototype._getCacheBind = function _getCacheBind(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 _setCache(name, action, hander) { var cache = this.cachedBind[name] = this.cachedBind[name] || {}; cache[action] = hander; }; Field.prototype._getCache = function _getCache(name, action) { var cache = this.cachedBind[name] || {}; return cache[action]; }; /** * NOTE: saveRef is async function. it will be called after render * @param {String} name name of component * @param {Function} component ref */ Field.prototype._saveRef = function _saveRef(name, component) { var key = name + '_field'; var autoUnmount = this.options.autoUnmount; if (!component && autoUnmount) { // 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]; this._setCache(name, key, cache); // after destroy, delete data delete this.fieldsMeta[name]; delete this.instance[name]; return; } // 2. _saveRef(B, ref) (eg: same name but different compoent may be here) if (autoUnmount && !this.fieldsMeta[name]) { this.fieldsMeta[name] = this._getCache(name, key); } // only one time here var field = this._get(name); if (field) { var ref = field.ref; if (ref) { if (typeof ref === 'string') { throw new Error('can not set string ref for ' + name); } ref(component); } this.instance[name] = component; } }; /** * validate one Component * @param {String} name name of Component * @param {Array} rule * @param {String} trigger onChange/onBlur/onItemClick/... */ Field.prototype._validate = function _validate(name, rule, trigger) { var _ref, _validate$validate, _this3 = this; var field = this._get(name); var value = field.value; field.state = 'loading'; var validate = this._getCache(name, trigger); validate && validate.abort(); validate = new _validate3.default((_ref = {}, _ref[name] = rule, _ref)); this._setCache(name, trigger, validate); validate.validate((_validate$validate = {}, _validate$validate[name] = value, _validate$validate), function (errors) { if (errors && errors.length) { field.errors = (0, _utils.getErrorStrs)(errors); field.state = 'error'; } else { field.errors = []; field.state = 'success'; } _this3._reRender(); }); }; Field.prototype.getValue = function getValue(name) { var field = this._get(name); if (field && 'value' in field) { return field.value; } return undefined; }; /** * 1. get values by names. * 2. ignore disabled value. * @param {Array} names */ Field.prototype.getValues = function getValues(names) { var _this4 = this; var fields = names || this.getNames(); var allValues = {}; fields.forEach(function (f) { if (f.disabled) { return; } if (!_this4.options.parseName) { allValues[f] = _this4.getValue(f); } else { allValues = (0, _utils.setIn)(allValues, f, _this4.getValue(f)); } }); return allValues; }; Field.prototype.setValue = function setValue(name, value) { var reRender = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; if (name in this.fieldsMeta) { this.fieldsMeta[name].value = value; } else { // if not exist, then new one this.fieldsMeta[name] = { value: value }; } reRender && this._reRender(); }; Field.prototype.setValues = function setValues() { var _this5 = this; var fieldsValue = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var reRender = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; if (!this.options.parseName) { Object.keys(fieldsValue).forEach(function (name) { _this5.setValue(name, fieldsValue[name], false); }); } else { var fields = this.getNames(); fields.forEach(function (name) { var value = (0, _utils.getIn)(fieldsValue, name); if (value !== undefined) { _this5.setValue(name, value, false); } }); } reRender && this._reRender(); }; Field.prototype.setError = function setError(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 }; } if (this.fieldsMeta[name].errors && this.fieldsMeta[name].errors.length > 0) { this.fieldsMeta[name].state = 'error'; } else { this.fieldsMeta[name].state = ''; } this._reRender(); }; Field.prototype.setErrors = function setErrors() { var _this6 = this; var fieldsErrors = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; Object.keys(fieldsErrors).forEach(function (name) { _this6.setError(name, fieldsErrors[name]); }); }; Field.prototype.getError = function getError(name) { var field = this._get(name); if (field && field.errors && field.errors.length) { return field.errors; } return null; }; Field.prototype.getErrors = function getErrors(names) { var _this7 = this; var fields = names || this.getNames(); var allErrors = {}; fields.forEach(function (f) { allErrors[f] = _this7.getError(f); }); return allErrors; }; Field.prototype.getState = function getState(name) { var field = this._get(name); if (field && field.state) { return field.state; } return ''; }; /** * validate by trigger * @param {Array} ns names * @param {Function} cb callback after validate */ Field.prototype.validate = function validate(ns, cb) { var _this8 = this; var _getParams = (0, _utils.getParams)(ns, cb), names = _getParams.names, callback = _getParams.callback; var fieldNames = names || this.getNames(); var descriptor = {}; var values = {}; var hasRule = false; for (var i = 0; i < fieldNames.length; i++) { var name = fieldNames[i]; var field = this._get(name); if (!field) { continue; } if (field.rules && field.rules.length) { descriptor[name] = field.rules; values[name] = this.getValue(name); hasRule = true; // clear error field.errors = []; field.state = ''; } } if (!hasRule) { callback && callback(null, this.getValues(fieldNames)); return; } var validate = new _validate3.default(descriptor, { first: this.options.first }); 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 = _this8._get(i); field.errors = (0, _utils.getErrorStrs)(errorsGroup[i].errors); field.state = 'error'; }); } // update to success which has no error for (var _i = 0; _i < fieldNames.length; _i++) { var _name = fieldNames[_i]; var _field = _this8._get(_name); if (_field.rules && !(errorsGroup && _name in errorsGroup)) { _field.state = 'success'; } } // eslint-disable-next-line callback-return callback && callback(errorsGroup, _this8.getValues(fieldNames)); _this8._reRender(); if (errorsGroup && _this8.options.scrollToFirstError) { var firstNode = void 0; var firstTop = void 0; for (var _i2 in errorsGroup) { if (errorsGroup.hasOwnProperty(_i2)) { var instance = _this8.instance[_i2]; var node = _reactDom2.default.findDOMNode(instance); if (!node) { return; } var top = node.offsetTop; if (firstTop === undefined || firstTop > top) { firstTop = top; firstNode = node; } } } if (firstNode) { if (typeof _this8.options.scrollToFirstError === 'number' && window && typeof window.scrollTo === 'function') { var offsetLeft = document && document.body && document.body.offsetLeft ? document.body.offsetLeft : 0; window.scrollTo(offsetLeft, firstTop + _this8.options.scrollToFirstError); } else if (firstNode.scrollIntoViewIfNeeded) { firstNode.scrollIntoViewIfNeeded(true); } } } }); }; Field.prototype._reset = function _reset(ns, backToDefault) { var _this9 = this; if (typeof ns === 'string') { ns = [ns]; } var changed = false; var names = ns || Object.keys(this.fieldsMeta); names.forEach(function (name) { var field = _this9._get(name); _this9.getValue(name); if (field) { changed = true; field.value = backToDefault ? field.initValue : undefined; field.state = ''; delete field.errors; delete field.rules; delete field.rulesMap; } }); if (changed) { this._reRender(); } }; Field.prototype.reset = function reset(ns) { var backToDefault = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (ns === true) { _util.log.deprecated('reset(true)', 'resetToDefault()', 'Field'); this.resetToDefault(); } else if (backToDefault === true) { _util.log.deprecated('reset(ns,true)', 'resetToDefault(ns)', 'Field'); this.resetToDefault(ns); } else { this._reset(ns, false); } }; Field.prototype.resetToDefault = function resetToDefault(ns) { this._reset(ns, true); }; // deprecated. TODO: remove in 2.0 version Field.prototype.isValidating = function isValidating(name) { _util.log.deprecated('isValidating', 'getState', 'Field'); return this.getState(name) === 'loading'; }; Field.prototype.getNames = function getNames() { var fieldsMeta = this.fieldsMeta; return fieldsMeta ? Object.keys(fieldsMeta).filter(function () { return true; }) : []; }; Field.prototype.remove = function remove(ns) { var _this10 = this; if (typeof ns === 'string') { ns = [ns]; } var names = ns || Object.keys(this.fieldsMeta); names.forEach(function (name) { if (name in _this10.fieldsMeta) { delete _this10.fieldsMeta[name]; } }); }; /** * splice in a Array * @param {String} keyMatch like name.{index} * @param {Number} startIndex index */ Field.prototype.spliceArray = function spliceArray(keyMatch, startIndex) { var _this11 = this; if (keyMatch.indexOf('{index}') === -1) { _util.log.warning('{index} not find in key'); return; } var reg = keyMatch.replace('{index}', '(\\d+)'); var keyReg = new RegExp('^' + reg + '$'); var list = []; var names = this.getNames(); names.forEach(function (n) { var ret = keyReg.exec(n); if (ret) { var index = parseInt(ret[1]); if (index > startIndex) { list.push({ index: index, name: n }); } } }); list = list.sort(function (a, b) { return a.index < b.index; }); // should be continuous array if (list.length > 0 && list[0].index === startIndex + 1) { list.forEach(function (l) { var n = keyMatch.replace('{index}', l.index - 1); _this11.fieldsMeta[n] = _this11.fieldsMeta[l.name]; }); delete this.fieldsMeta[list[list.length - 1].name]; this._reRender(); } }; Field.prototype._resetError = function _resetError(name) { var field = this._get(name); delete field.errors; //清空错误 field.state = ''; }; //trigger rerender Field.prototype._reRender = function _reRender() { if (this.com) { if (!this.options.forceUpdate && this.com.setState) { this.com.setState({}); } else if (this.com.forceUpdate) { this.com.forceUpdate(); //forceUpdate 对性能有较大的影响,成指数上升 } } }; Field.prototype._get = function _get(name) { return name in this.fieldsMeta ? this.fieldsMeta[name] : null; }; return Field; }(); exports.default = Field; module.exports = exports['default'];