123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- import warning from '../libs/warning'
- import { set } from '../shared/util'
- export function isNullValue(value) {
- return value === null || value === undefined
- }
- /**
- * fix: setting data field "xxx" to undefined is invalid.
- *
- * @param {*} value
- * @returns
- */
- export function fixNullValue(value) {
- return isNullValue(value) ? null : value
- }
- export function flattenArray(arr) {
- return Array.prototype.concat.apply([], arr)
- }
- export function treeTraverse(path = '', tree, isLeafNode, errorMessage, callback) {
- if (isLeafNode(path, tree)) {
- callback(path, tree)
- } else if (tree === undefined || tree === null) {
- // Do nothing
- } else if (Array.isArray(tree)) {
- tree.forEach((subTree, index) => treeTraverse(
- `${path}[${index}]`,
- subTree,
- isLeafNode,
- errorMessage,
- callback
- ))
- } else { // It's object and not a leaf node
- if (typeof tree !== 'object') {
- warning(false, errorMessage)
- return
- }
- Object.keys(tree).forEach(subTreeKey => {
- const subTree = tree[subTreeKey]
- treeTraverse(
- `${path}${path ? '.' : ''}${subTreeKey}`,
- subTree,
- isLeafNode,
- errorMessage,
- callback
- )
- })
- }
- }
- export function flattenFields(maybeNestedFields, isLeafNode, errorMessage) {
- const fields = {}
- treeTraverse(undefined, maybeNestedFields, isLeafNode, errorMessage, (path, node) => {
- fields[path] = node
- })
- return fields
- }
- export function normalizeValidateRules(validate, rules, validateTrigger) {
- const validateRules = validate.map((item) => {
- const newItem = {
- ...item,
- trigger: item.trigger || [],
- }
- if (typeof newItem.trigger === 'string') {
- newItem.trigger = [newItem.trigger]
- }
- return newItem
- })
- if (rules) {
- validateRules.push({
- trigger: validateTrigger ? [].concat(validateTrigger) : [],
- rules,
- })
- }
- return validateRules
- }
- export function getValidateTriggers(validateRules) {
- return validateRules
- .filter(item => !!item.rules && item.rules.length)
- .map(item => item.trigger)
- .reduce((pre, curr) => pre.concat(curr), [])
- }
- export function getParams(ns, opt, cb) {
- let names = ns
- let options = opt
- let callback = cb
- if (cb === undefined) {
- if (typeof names === 'function') {
- callback = names
- options = {}
- names = undefined
- } else if (Array.isArray(names)) {
- if (typeof options === 'function') {
- callback = options
- options = {}
- } else {
- options = options || {}
- }
- } else {
- callback = options
- options = names || {}
- names = undefined
- }
- }
- return {
- names,
- options,
- callback,
- }
- }
- export function isEmptyObject(obj) {
- return Object.keys(obj).length === 0
- }
- export function hasRules(validate) {
- if (validate) {
- return validate.some((item) => {
- return item.rules && item.rules.length
- })
- }
- return false
- }
- export function getErrorStrs(errors) {
- if (errors) {
- return errors.map((e) => {
- if (e && e.message) {
- return e.message
- }
- return e
- })
- }
- return errors
- }
- export function startsWith(str, prefix) {
- return str.lastIndexOf(prefix, 0) === 0
- }
- export function getValueFromEvent(e) {
- const getValue = (target) =>
- target.type === 'checkbox' ? target.checked : target.value
- // To support custom element
- if (!e || !e.detail) {
- if (!!e) {
- return getValue(e)
- }
- return e
- }
- return getValue(e.detail)
- }
- /**
- * ## 转换校验规则,使之支持 async-validator
- * 注意:由于小程序 data 不支持传入 RegExp & function,所以在设置 rules 需要传入字符串映射!!!
- *
- * @example
- * ```
- * // 手机号验证
- * rules="{{ [{ required: true, pattern: '1[3456789]\d{9}' }] }}"
- * ```
- * @example
- * ```
- * // 自定义验证
- * rules="{{ [{ required: true, validator: 'checkMobile' }] }}"
- *
- * Page({
- * checkMobile(rule, value) {
- * if (value) {
- * return Promise.resolve()
- * }
- * return Promise.reject(new Error('手机号不能为空!'))
- * }
- * })
- * ```
- * @example
- * ```
- * // 校验前去除空格
- * rules="{{ [{ required: true, transform: 'trim' }] }}"
- *
- * Page({
- * trim(value) {
- * return value.trim()
- * }
- * })
- * ```
- * @export
- * @param {array} rules 校验规则,设置字段的校验逻辑
- * @param {object} vm 当前页面的实例对象
- * @return {array}
- */
- export function transformRules(rules, vm = getCurrentPages()[getCurrentPages().length - 1]) {
- return rules.map((rule) => {
- const cloneRule = { ...rule }
- // 正则表达式校验
- if (typeof rule.pattern === 'string') {
- cloneRule.pattern = new RegExp(rule.pattern)
- }
- // 校验前转换字段值
- if (typeof rule.transform === 'string' && vm && vm[rule.transform]) {
- cloneRule.transform = vm[rule.transform]
- }
- // 自定义校验,接收 Promise 作为返回值。
- if (typeof rule.validator === 'string' && vm && vm[rule.validator]) {
- cloneRule.validator = vm[rule.validator]
- }
- return cloneRule
- })
- }
- /**
- * ## Replace with template.
- *
- * @example
- * ```
- * `I'm ${name}` + { name: 'bamboo' } = I'm bamboo
- * ```
- * @export
- * @param {*} template
- * @param {*} kv
- * @return {*}
- */
- export function replaceMessage(template, kv) {
- return template.replace(/\$\{\w+\}/g, (str) => {
- const key = str.slice(2, -1);
- return kv[key];
- });
- }
- class Field {
- constructor(fields) {
- Object.assign(this, fields);
- }
- }
- export function isFormField(obj) {
- return obj instanceof Field;
- }
- export function createFormField(field) {
- if (isFormField(field)) {
- return field;
- }
- return new Field(field);
- }
- export function internalFlattenFields(fields) {
- return flattenFields(
- fields,
- (_, node) => isFormField(node),
- 'You must wrap field data with `createFormField`.'
- )
- }
- class FieldsStore {
- constructor(fields = {}) {
- this.fields = internalFlattenFields(fields)
- this.fieldsMeta = {}
- }
- updateFields(fields) {
- this.fields = internalFlattenFields(fields)
- }
- flattenRegisteredFields(fields) {
- const validFieldsName = this.getAllFieldsName();
- return flattenFields(
- fields,
- path => validFieldsName.indexOf(path) >= 0,
- 'You cannot set a form field before rendering a field associated with the value.'
- )
- }
- setFieldsInitialValue = (initialValues) => {
- const flattenedInitialValues = this.flattenRegisteredFields(initialValues)
- const fieldsMeta = this.fieldsMeta
- Object.keys(flattenedInitialValues).forEach(name => {
- if (fieldsMeta[name]) {
- const fieldMeta = this.getFieldMeta(name)
- const initValue = fieldMeta.initialValue
- if (isNullValue(initValue)) {
- this.setFieldMeta(name, {
- ...fieldMeta,
- initialValue: fixNullValue(flattenedInitialValues[name]),
- });
- }
- }
- })
- }
- setFields(fields) {
- const nowFields = {
- ...this.fields,
- ...fields,
- }
- this.fields = nowFields
- }
- resetFields(ns) {
- const { fieldsMeta } = this
- const names = ns ?
- this.getValidFieldsFullName(ns) :
- this.getAllFieldsName()
- return names.reduce((acc, name) => {
- const fieldMeta = fieldsMeta[name]
- if (fieldMeta) {
- acc[name] = { value: fixNullValue(fieldMeta.initialValue) }
- }
- return acc
- }, {})
- }
- setFieldMeta(name, meta) {
- this.fieldsMeta[name] = meta
- }
- setFieldsAsDirty() {
- Object.keys(this.fields).forEach((name) => {
- const field = this.fields[name]
- const fieldMeta = this.fieldsMeta[name]
- if (field && fieldMeta && hasRules(fieldMeta.validate)) {
- this.fields[name] = {
- ...field,
- dirty: true,
- }
- }
- })
- }
- getFieldMeta(name) {
- this.fieldsMeta[name] = this.fieldsMeta[name] || {}
- return this.fieldsMeta[name]
- }
- getValueFromFields(name, fields) {
- const field = fields[name]
- if (field && 'value' in field) {
- return fixNullValue(field.value)
- }
- const fieldMeta = this.getFieldMeta(name)
- return fieldMeta && fixNullValue(fieldMeta.initialValue)
- }
- getAllValues = () => {
- const { fieldsMeta, fields } = this
- return Object.keys(fieldsMeta)
- .reduce((acc, name) => set(acc, name, this.getValueFromFields(name, fields)), {})
- }
- getValidFieldsName() {
- const { fieldsMeta } = this
- return fieldsMeta ?
- Object.keys(fieldsMeta).filter(name => !this.getFieldMeta(name).hidden) :
- []
- }
- getAllFieldsName() {
- const { fieldsMeta } = this
- return fieldsMeta ? Object.keys(fieldsMeta) : []
- }
- getValidFieldsFullName(maybePartialName) {
- const maybePartialNames = Array.isArray(maybePartialName) ?
- maybePartialName : [maybePartialName]
- return this.getValidFieldsName()
- .filter(fullName => maybePartialNames.some(partialName => (
- fullName === partialName || (
- startsWith(fullName, partialName) &&
- ['.', '['].indexOf(fullName[partialName.length]) >= 0
- )
- )))
- }
- getFieldValuePropValue(fieldMeta) {
- const { name, valuePropName } = fieldMeta
- const field = this.getField(name)
- const fieldValue = 'value' in field ? field.value : fieldMeta.initialValue
- return {
- [valuePropName]: fixNullValue(fieldValue),
- }
- }
- getField(name) {
- return {
- ...this.fields[name],
- name,
- }
- }
- getNotCollectedFields() {
- const fieldsName = this.getValidFieldsName();
- return fieldsName
- .filter(name => !this.fields[name])
- .map(name => ({
- name,
- dirty: false,
- value: this.getFieldMeta(name).initialValue,
- }))
- .reduce((acc, field) => set(acc, field.name, createFormField(field)), {});
- }
- getNestedAllFields() {
- return Object.keys(this.fields)
- .reduce(
- (acc, name) => set(acc, name, createFormField(this.fields[name])),
- this.getNotCollectedFields()
- );
- }
- getFieldMember(name, member) {
- return this.getField(name)[member]
- }
- getNestedFields(names, getter) {
- const fields = names || this.getValidFieldsName()
- return fields.reduce((acc, f) => set(acc, f, getter(f)), {})
- }
- getNestedField(name, getter) {
- const fullNames = this.getValidFieldsFullName(name)
- if (
- fullNames.length === 0 || // Not registered
- (fullNames.length === 1 && fullNames[0] === name) // Name already is full name.
- ) {
- return getter(name)
- }
- const isArrayValue = fullNames[0][name.length] === '['
- const suffixNameStartIndex = isArrayValue ? name.length : name.length + 1
- return fullNames
- .reduce(
- (acc, fullName) => set(
- acc,
- fullName.slice(suffixNameStartIndex),
- getter(fullName)
- ),
- isArrayValue ? [] : {}
- )
- }
- getFieldsValue = (names) => {
- return this.getNestedFields(names, this.getFieldValue)
- }
- getFieldValue = (name) => {
- const { fields } = this
- return this.getNestedField(name, (fullName) => this.getValueFromFields(fullName, fields))
- }
- getFieldsError = (names) => {
- return this.getNestedFields(names, this.getFieldError)
- }
- getFieldError = (name) => {
- return this.getNestedField(
- name,
- (fullName) => getErrorStrs(this.getFieldMember(fullName, 'errors'))
- )
- }
- isFieldValidating = (name) => {
- return this.getFieldMember(name, 'validating')
- }
- isFieldsValidating = (ns) => {
- const names = ns || this.getValidFieldsName()
- return names.some((n) => this.isFieldValidating(n))
- }
- isFieldTouched = (name) => {
- return this.getFieldMember(name, 'touched')
- }
- isFieldsTouched = (ns) => {
- const names = ns || this.getValidFieldsName()
- return names.some((n) => this.isFieldTouched(n))
- }
- clearField(name) {
- delete this.fields[name]
- delete this.fieldsMeta[name]
- }
- }
- export default function createFieldsStore(fields) {
- return new FieldsStore(fields)
- }
|