refactor: Validation callbacks logic simplification, typehints
This commit is contained in:
parent
99c3f8a4cd
commit
45f29ff27a
@ -36,11 +36,11 @@ class FileUpload {
|
||||
public context: ObjectType
|
||||
public results: any[] | boolean
|
||||
|
||||
constructor (input: DataTransfer, context: ObjectType, options: ObjectType = {}) {
|
||||
constructor (input: DataTransfer, context: ObjectType = {}, options: ObjectType = {}) {
|
||||
this.input = input
|
||||
this.fileList = input.files
|
||||
this.files = []
|
||||
this.options = { ...{ mimes: {} }, ...options }
|
||||
this.options = { mimes: {}, ...options }
|
||||
this.results = false
|
||||
this.context = context
|
||||
if (Array.isArray(this.fileList)) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { VueConstructor } from 'vue'
|
||||
|
||||
import library from './libs/library'
|
||||
import rules from './libs/rules'
|
||||
import rules from './validation/rules'
|
||||
import mimes from './libs/mimes'
|
||||
import FileUpload from './FileUpload'
|
||||
import RuleValidationMessages from './RuleValidationMessages'
|
||||
@ -13,7 +13,7 @@ import FormularioForm from '@/FormularioForm.vue'
|
||||
import FormularioInput from '@/FormularioInput.vue'
|
||||
import FormularioGrouping from './FormularioGrouping.vue'
|
||||
import { ObjectType } from '@/common.types'
|
||||
import { ValidationContext } from '@/validation.types'
|
||||
import { ValidationContext } from '@/validation/types'
|
||||
|
||||
interface ErrorHandler {
|
||||
(error: any, formName?: string): any
|
||||
|
@ -25,7 +25,8 @@ import {
|
||||
Watch,
|
||||
} from 'vue-property-decorator'
|
||||
import { shallowEqualObjects, parseRules, snakeToCamel, has, arrayify, groupBails } from './libs/utils'
|
||||
import { ValidationError } from '@/validation.types'
|
||||
import { ValidationError } from '@/validation/types'
|
||||
import { ObjectType } from '@/common.types'
|
||||
|
||||
const ERROR_BEHAVIOR = {
|
||||
BLUR: 'blur',
|
||||
@ -87,12 +88,12 @@ export default class FormularioInput extends Vue {
|
||||
@Prop({
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
}) validationRules!: Object
|
||||
}) validationRules!: ObjectType
|
||||
|
||||
@Prop({
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
}) validationMessages!: Object
|
||||
}) validationMessages!: ObjectType
|
||||
|
||||
@Prop({
|
||||
type: [Array, String, Boolean],
|
||||
@ -112,16 +113,16 @@ export default class FormularioInput extends Vue {
|
||||
@Prop({ default: true }) preventWindowDrops!: boolean
|
||||
|
||||
defaultId: string = this.$formulario.nextId(this)
|
||||
localAttributes: Object = {}
|
||||
localAttributes: ObjectType = {}
|
||||
localErrors: ValidationError[] = []
|
||||
proxy: Object = this.getInitialValue()
|
||||
proxy: ObjectType = this.getInitialValue()
|
||||
behavioralErrorVisibility: boolean = this.errorBehavior === 'live'
|
||||
formShouldShowErrors: boolean = false
|
||||
validationErrors: [] = []
|
||||
pendingValidation: Promise = Promise.resolve()
|
||||
// These registries are used for injected messages registrants only (mostly internal).
|
||||
ruleRegistry: [] = []
|
||||
messageRegistry: Object = {}
|
||||
messageRegistry: ObjectType = {}
|
||||
|
||||
get context () {
|
||||
return this.defineModel({
|
||||
@ -322,6 +323,7 @@ export default class FormularioInput extends Vue {
|
||||
this.performValidation()
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
beforeDestroy () {
|
||||
if (!this.disableErrors && typeof this.removeErrorObserver === 'function') {
|
||||
this.removeErrorObserver(this.setErrors)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Formulario } from '@/Formulario'
|
||||
import FormularioInput from '@/FormularioInput.vue'
|
||||
import { ValidationContext } from '@/validation.types'
|
||||
import { ValidationContext } from '@/validation/types'
|
||||
|
||||
/**
|
||||
* This is an object of functions that each produce valid responses. There's no
|
||||
@ -216,6 +216,6 @@ const validationMessages = {
|
||||
* This creates a vue-formulario plugin that can be imported and used on each
|
||||
* project.
|
||||
*/
|
||||
export default function (instance: Formulario) {
|
||||
export default function (instance: Formulario): void {
|
||||
instance.extend({ validationMessages })
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ import FormularioInput from '@/FormularioInput.vue'
|
||||
* important for features such as grouped fields.
|
||||
*/
|
||||
export default class Registry {
|
||||
public ctx: FormularioForm
|
||||
private registry: Map<string, FormularioForm>
|
||||
private ctx: FormularioForm
|
||||
private registry: Map<string, FormularioInput>
|
||||
|
||||
/**
|
||||
* Create a new registry of components.
|
||||
@ -23,7 +23,7 @@ export default class Registry {
|
||||
/**
|
||||
* Add an item to the registry.
|
||||
*/
|
||||
add (name: string, component: FormularioForm) {
|
||||
add (name: string, component: FormularioInput) {
|
||||
this.registry.set(name, component)
|
||||
return this
|
||||
}
|
||||
@ -52,7 +52,7 @@ export default class Registry {
|
||||
/**
|
||||
* Get a particular registry value.
|
||||
*/
|
||||
get (key: string): FormularioForm | undefined {
|
||||
get (key: string): FormularioInput | undefined {
|
||||
return this.registry.get(key)
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,18 @@
|
||||
// @ts-ignore
|
||||
import isUrl from 'is-url'
|
||||
import FileUpload from '../FileUpload'
|
||||
import { shallowEqualObjects, regexForFormat, has } from './utils'
|
||||
import { shallowEqualObjects, regexForFormat, has } from '@/libs/utils'
|
||||
import { ObjectType } from '@/common.types'
|
||||
|
||||
interface ValidatableData {
|
||||
value: any,
|
||||
}
|
||||
|
||||
interface ConfirmValidatableData extends ValidatableData {
|
||||
getFormValues: () => ObjectType,
|
||||
name: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Library of rules
|
||||
*/
|
||||
@ -11,23 +20,23 @@ export default {
|
||||
/**
|
||||
* Rule: the value must be "yes", "on", "1", or true
|
||||
*/
|
||||
accepted ({ value }: { value: any }) {
|
||||
accepted ({ value }: ValidatableData): Promise<boolean> {
|
||||
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if a value is after a given date. Defaults to current time
|
||||
*/
|
||||
after ({ value }: { value: string }, compare: string | false = false) {
|
||||
const timestamp = compare !== false ? Date.parse(compare) : new Date()
|
||||
const fieldValue = Date.parse(value)
|
||||
after ({ value }: { value: Date|string }, compare: string | false = false): Promise<boolean> {
|
||||
const timestamp = compare !== false ? Date.parse(compare) : Date.now()
|
||||
const fieldValue = value instanceof Date ? value.getTime() : Date.parse(value)
|
||||
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue > timestamp))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if the value is only alpha
|
||||
*/
|
||||
alpha ({ value }: { value: string }, set: string = 'default') {
|
||||
alpha ({ value }: { value: string }, set: string = 'default'): Promise<boolean> {
|
||||
const sets = {
|
||||
default: /^[a-zA-ZÀ-ÖØ-öø-ÿ]+$/,
|
||||
latin: /^[a-zA-Z]+$/
|
||||
@ -40,7 +49,7 @@ export default {
|
||||
/**
|
||||
* Rule: checks if the value is alpha numeric
|
||||
*/
|
||||
alphanumeric ({ value }: { value: string }, set = 'default') {
|
||||
alphanumeric ({ value }: { value: string }, set = 'default'): Promise<boolean> {
|
||||
const sets = {
|
||||
default: /^[a-zA-Z0-9À-ÖØ-öø-ÿ]+$/,
|
||||
latin: /^[a-zA-Z0-9]+$/
|
||||
@ -53,16 +62,16 @@ export default {
|
||||
/**
|
||||
* Rule: checks if a value is after a given date. Defaults to current time
|
||||
*/
|
||||
before ({ value }: { value: string }, compare: string | false = false) {
|
||||
const timestamp = compare !== false ? Date.parse(compare) : new Date()
|
||||
const fieldValue = Date.parse(value)
|
||||
before ({ value }: { value: Date|string }, compare: string|false = false): Promise<boolean> {
|
||||
const timestamp = compare !== false ? Date.parse(compare) : Date.now()
|
||||
const fieldValue = value instanceof Date ? value.getTime() : Date.parse(value)
|
||||
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue < timestamp))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if the value is between two other values
|
||||
*/
|
||||
between ({ value }: { value: string | number }, from: number = 0, to: number = 10, force: string) {
|
||||
between ({ value }: { value: string|number }, from: number|any = 0, to: number|any = 10, force?: string): Promise<boolean> {
|
||||
return Promise.resolve((() => {
|
||||
if (from === null || to === null || isNaN(from) || isNaN(to)) {
|
||||
return false
|
||||
@ -85,7 +94,7 @@ export default {
|
||||
* Confirm that the value of one field is the same as another, mostly used
|
||||
* for password confirmations.
|
||||
*/
|
||||
confirm ({ value, getFormValues, name }: { value: any, getFormValues: () => ObjectType, name: string }, field: string) {
|
||||
confirm ({ value, getFormValues, name }: ConfirmValidatableData, field?: string): Promise<boolean> {
|
||||
return Promise.resolve((() => {
|
||||
const formValues = getFormValues()
|
||||
let confirmationFieldName = field
|
||||
@ -100,64 +109,51 @@ export default {
|
||||
* Rule: ensures the value is a date according to Date.parse(), or a format
|
||||
* regex.
|
||||
*/
|
||||
date ({ value }: { value: string }, format: string | false = false) {
|
||||
return Promise.resolve((() => {
|
||||
if (format) {
|
||||
return regexForFormat(format).test(value)
|
||||
}
|
||||
return !isNaN(Date.parse(value))
|
||||
})())
|
||||
date ({ value }: { value: string }, format: string | false = false): Promise<boolean> {
|
||||
return Promise.resolve(format ? regexForFormat(format).test(value) : !isNaN(Date.parse(value)))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: tests
|
||||
*/
|
||||
email ({ value }: { value: string}) {
|
||||
email ({ value }: { value: string }): Promise<boolean> {
|
||||
if (!value) {
|
||||
return Promise.resolve(() => { return true })
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
const isEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i
|
||||
const isEmail = /^(([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i
|
||||
return Promise.resolve(isEmail.test(value))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: Value ends with one of the given Strings
|
||||
*/
|
||||
endsWith: function ({ value }: any, ...stack: any[]) {
|
||||
endsWith ({ value }: { value: any }, ...stack: any[]): Promise<boolean> {
|
||||
if (!value) {
|
||||
return Promise.resolve(() => { return true })
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
return Promise.resolve((() => {
|
||||
if (typeof value === 'string' && stack.length) {
|
||||
return stack.find(item => {
|
||||
return value.endsWith(item)
|
||||
}) !== undefined
|
||||
} else if (typeof value === 'string' && stack.length === 0) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})())
|
||||
if (typeof value === 'string') {
|
||||
return Promise.resolve(stack.length === 0 || stack.some(str => value.endsWith(str)))
|
||||
}
|
||||
|
||||
return Promise.resolve(false)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: Value is in an array (stack).
|
||||
*/
|
||||
in: function ({ value }: any, ...stack: any[]) {
|
||||
return Promise.resolve(stack.find(item => {
|
||||
if (typeof item === 'object') {
|
||||
return shallowEqualObjects(item, value)
|
||||
}
|
||||
return item === value
|
||||
}) !== undefined)
|
||||
in ({ value }: { value: any }, ...stack: any[]): Promise<boolean> {
|
||||
return Promise.resolve(stack.some(item => {
|
||||
return typeof item === 'object' ? shallowEqualObjects(item, value) : item === value
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: Match the value against a (stack) of patterns or strings
|
||||
*/
|
||||
matches: function ({ value }: any, ...stack: any[]) {
|
||||
matches ({ value }: { value: any }, ...stack: any[]): Promise<boolean> {
|
||||
return Promise.resolve(!!stack.find(pattern => {
|
||||
if (typeof pattern === 'string' && pattern.substr(0, 1) === '/' && pattern.substr(-1) === '/') {
|
||||
pattern = new RegExp(pattern.substr(1, pattern.length - 2))
|
||||
@ -172,25 +168,22 @@ export default {
|
||||
/**
|
||||
* Check the file type is correct.
|
||||
*/
|
||||
mime: function ({ value }: any, ...types: string[]) {
|
||||
return Promise.resolve((() => {
|
||||
if (value instanceof FileUpload) {
|
||||
const fileList = value.getFiles()
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const file = fileList[i].file
|
||||
if (!types.includes(file.type)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})())
|
||||
mime ({ value }: { value: any }, ...types: string[]): Promise<boolean> {
|
||||
if (value instanceof FileUpload) {
|
||||
const files = value.getFiles()
|
||||
const isMimeCorrect = (file: File) => types.includes(file.type)
|
||||
const allValid: boolean = files.reduce((valid: boolean, { file }) => valid && isMimeCorrect(file), true)
|
||||
|
||||
return Promise.resolve(allValid)
|
||||
}
|
||||
|
||||
return Promise.resolve(true)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check the minimum value of a particular.
|
||||
*/
|
||||
min: function ({ value }: any, minimum = 1, force: string) {
|
||||
min ({ value }: { value: any }, minimum: number | any = 1, force?: string): Promise<boolean> {
|
||||
return Promise.resolve((() => {
|
||||
if (Array.isArray(value)) {
|
||||
minimum = !isNaN(minimum) ? Number(minimum) : minimum
|
||||
@ -211,10 +204,10 @@ export default {
|
||||
/**
|
||||
* Check the maximum value of a particular.
|
||||
*/
|
||||
max: function ({ value }: any, maximum = 10, force: string) {
|
||||
max ({ value }: { value: any }, maximum: string | number = 10, force?: string): Promise<boolean> {
|
||||
return Promise.resolve((() => {
|
||||
if (Array.isArray(value)) {
|
||||
maximum = !isNaN(maximum) ? Number(maximum) : maximum
|
||||
maximum = !isNaN(Number(maximum)) ? Number(maximum) : maximum
|
||||
return value.length <= maximum
|
||||
}
|
||||
if ((!isNaN(value) && force !== 'length') || force === 'value') {
|
||||
@ -232,26 +225,23 @@ export default {
|
||||
/**
|
||||
* Rule: Value is not in stack.
|
||||
*/
|
||||
not: function ({ value }: any, ...stack: any[]) {
|
||||
return Promise.resolve(stack.find(item => {
|
||||
if (typeof item === 'object') {
|
||||
return shallowEqualObjects(item, value)
|
||||
}
|
||||
return item === value
|
||||
}) === undefined)
|
||||
not ({ value }: { value: any }, ...stack: any[]): Promise<boolean> {
|
||||
return Promise.resolve(!stack.some(item => {
|
||||
return typeof item === 'object' ? shallowEqualObjects(item, value) : item === value
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if the value is only alpha numeric
|
||||
*/
|
||||
number ({ value }: { value: any }) {
|
||||
return Promise.resolve(!isNaN(value))
|
||||
number ({ value }: { value: any }): Promise<boolean> {
|
||||
return Promise.resolve(!isNaN(Number(value)))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: must be a value
|
||||
*/
|
||||
required ({ value }: any, isRequired: string|boolean = true) {
|
||||
required ({ value }: { value: any }, isRequired: string|boolean = true): Promise<boolean> {
|
||||
return Promise.resolve((() => {
|
||||
if (!isRequired || ['no', 'false'].includes(isRequired as string)) {
|
||||
return true
|
||||
@ -275,32 +265,29 @@ export default {
|
||||
/**
|
||||
* Rule: Value starts with one of the given Strings
|
||||
*/
|
||||
startsWith ({ value }: { value: any }, ...stack: any[]) {
|
||||
startsWith ({ value }: { value: any }, ...stack: string[]): Promise<boolean> {
|
||||
if (!value) {
|
||||
return Promise.resolve(() => { return true })
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
return Promise.resolve((() => {
|
||||
if (typeof value === 'string' && stack.length) {
|
||||
return stack.find(item => value.startsWith(item)) !== undefined
|
||||
} else if (typeof value === 'string' && stack.length === 0) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})())
|
||||
if (typeof value === 'string') {
|
||||
return Promise.resolve(stack.length === 0 || stack.some(str => value.startsWith(str)))
|
||||
}
|
||||
|
||||
return Promise.resolve(false)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if a string is a valid url
|
||||
*/
|
||||
url ({ value }: { value: string }) {
|
||||
url ({ value }: { value: string }): Promise<boolean> {
|
||||
return Promise.resolve(isUrl(value))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: not a true rule — more like a compiler flag.
|
||||
*/
|
||||
bail () {
|
||||
bail (): Promise<boolean> {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
}
|
@ -14,8 +14,8 @@ describe('FormularioGrouping', () => {
|
||||
slots: {
|
||||
default: `
|
||||
<FormularioGrouping name="sub">
|
||||
<FormularioInput name="text" v-slot="vSlot">
|
||||
<input type="text" v-model="vSlot.context.model">
|
||||
<FormularioInput name="text" v-slot="{ context }">
|
||||
<input type="text" v-model="context.model">
|
||||
</FormularioInput>
|
||||
</FormularioGrouping>
|
||||
`
|
||||
|
@ -1,4 +1,4 @@
|
||||
import rules from '@/libs/rules'
|
||||
import rules from '@/validation/rules.ts'
|
||||
import FileUpload from '../../src/FileUpload'
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { parseRules, parseLocale, regexForFormat, cloneDeep, isScalar, snakeToCamel, groupBails } from '@/libs/utils'
|
||||
import rules from '@/libs/rules'
|
||||
import FileUpload from '@/FileUpload';
|
||||
import rules from '@/validation/rules.ts'
|
||||
import FileUpload from '@/FileUpload'
|
||||
|
||||
describe('parseRules', () => {
|
||||
it('parses single string rules, returning empty arguments array', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user