2020-05-22 14:22:56 +03:00
|
|
|
|
<template>
|
|
|
|
|
<div
|
|
|
|
|
class="formulario-input"
|
|
|
|
|
:data-has-errors="hasErrors"
|
|
|
|
|
:data-is-showing-errors="hasVisibleErrors"
|
|
|
|
|
:data-type="type"
|
|
|
|
|
>
|
2020-10-09 22:58:28 +03:00
|
|
|
|
<slot
|
|
|
|
|
:id="id"
|
|
|
|
|
:context="context"
|
|
|
|
|
:errors="errors"
|
|
|
|
|
:validationErrors="validationErrors"
|
|
|
|
|
/>
|
2020-05-22 14:22:56 +03:00
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
<script lang="ts">
|
|
|
|
|
import Vue from 'vue'
|
|
|
|
|
import {
|
|
|
|
|
Component,
|
|
|
|
|
Inject,
|
|
|
|
|
Model,
|
|
|
|
|
Prop,
|
|
|
|
|
Provide,
|
|
|
|
|
Watch,
|
|
|
|
|
} from 'vue-property-decorator'
|
2020-05-22 14:22:56 +03:00
|
|
|
|
import { shallowEqualObjects, parseRules, snakeToCamel, has, arrayify, groupBails } from './libs/utils'
|
2020-10-11 00:52:18 +03:00
|
|
|
|
import { ValidationError } from '@/validation/types'
|
|
|
|
|
import { ObjectType } from '@/common.types'
|
2020-05-22 14:22:56 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
const ERROR_BEHAVIOR = {
|
|
|
|
|
BLUR: 'blur',
|
|
|
|
|
LIVE: 'live',
|
|
|
|
|
SUBMIT: 'submit',
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-11 15:10:21 +03:00
|
|
|
|
@Component({ inheritAttrs: false })
|
2020-10-10 22:45:28 +03:00
|
|
|
|
export default class FormularioInput extends Vue {
|
2020-10-11 15:10:21 +03:00
|
|
|
|
@Inject({ default: undefined }) formularioSetter!: Function|undefined
|
2020-10-10 22:45:28 +03:00
|
|
|
|
@Inject({ default: () => () => ({}) }) formularioFieldValidation!: Function
|
2020-10-11 15:10:21 +03:00
|
|
|
|
@Inject({ default: undefined }) formularioRegister!: Function|undefined
|
|
|
|
|
@Inject({ default: undefined }) formularioDeregister!: Function|undefined
|
2020-10-10 22:45:28 +03:00
|
|
|
|
@Inject({ default: () => () => ({}) }) getFormValues!: Function
|
2020-10-11 15:10:21 +03:00
|
|
|
|
@Inject({ default: undefined }) observeErrors!: Function|undefined
|
|
|
|
|
@Inject({ default: undefined }) removeErrorObserver!: Function|undefined
|
2020-10-10 22:45:28 +03:00
|
|
|
|
@Inject({ default: '' }) path!: string
|
|
|
|
|
|
|
|
|
|
@Provide() formularioRegisterRule = this.registerRule
|
|
|
|
|
@Provide() formularioRemoveRule = this.removeRule
|
|
|
|
|
|
|
|
|
|
@Model('input', {
|
|
|
|
|
default: '',
|
|
|
|
|
}) formularioValue: any
|
|
|
|
|
|
|
|
|
|
@Prop({
|
|
|
|
|
type: [String, Number, Boolean],
|
|
|
|
|
default: false,
|
2020-10-11 15:10:21 +03:00
|
|
|
|
}) id!: string|number|boolean
|
2020-10-10 22:45:28 +03:00
|
|
|
|
|
|
|
|
|
@Prop({ default: 'text' }) type!: string
|
2020-10-15 10:50:07 +03:00
|
|
|
|
@Prop({ required: true }) name!: string|boolean
|
2020-10-10 22:45:28 +03:00
|
|
|
|
@Prop({ default: false }) value!: any
|
|
|
|
|
|
|
|
|
|
@Prop({
|
|
|
|
|
type: [String, Boolean, Array],
|
|
|
|
|
default: false,
|
|
|
|
|
}) validation
|
|
|
|
|
|
|
|
|
|
@Prop({
|
|
|
|
|
type: [String, Boolean],
|
|
|
|
|
default: false,
|
2020-10-11 15:10:21 +03:00
|
|
|
|
}) validationName!: string|boolean
|
2020-10-10 22:45:28 +03:00
|
|
|
|
|
|
|
|
|
@Prop({
|
|
|
|
|
type: Object,
|
|
|
|
|
default: () => ({}),
|
2020-10-11 00:52:18 +03:00
|
|
|
|
}) validationRules!: ObjectType
|
2020-10-10 22:45:28 +03:00
|
|
|
|
|
|
|
|
|
@Prop({
|
|
|
|
|
type: Object,
|
|
|
|
|
default: () => ({}),
|
2020-10-11 00:52:18 +03:00
|
|
|
|
}) validationMessages!: ObjectType
|
2020-10-10 22:45:28 +03:00
|
|
|
|
|
|
|
|
|
@Prop({
|
|
|
|
|
type: [Array, String, Boolean],
|
|
|
|
|
default: false,
|
2020-10-11 15:10:21 +03:00
|
|
|
|
}) errors!: []|string|boolean
|
2020-10-10 22:45:28 +03:00
|
|
|
|
|
|
|
|
|
@Prop({
|
|
|
|
|
type: String,
|
|
|
|
|
default: ERROR_BEHAVIOR.BLUR,
|
|
|
|
|
validator: value => [ERROR_BEHAVIOR.BLUR, ERROR_BEHAVIOR.LIVE, ERROR_BEHAVIOR.SUBMIT].includes(value)
|
|
|
|
|
}) errorBehavior!: string
|
|
|
|
|
|
|
|
|
|
@Prop({ default: false }) showErrors!: boolean
|
|
|
|
|
@Prop({ default: false }) disableErrors!: boolean
|
|
|
|
|
@Prop({ default: true }) preventWindowDrops!: boolean
|
2020-10-11 15:10:21 +03:00
|
|
|
|
@Prop({ default: 'preview' }) imageBehavior!: string
|
|
|
|
|
@Prop({ default: false }) uploader!: Function|Object|boolean
|
|
|
|
|
@Prop({ default: false }) uploadUrl!: string|boolean
|
|
|
|
|
@Prop({ default: 'live' }) uploadBehavior!: string
|
2020-10-10 22:45:28 +03:00
|
|
|
|
|
|
|
|
|
defaultId: string = this.$formulario.nextId(this)
|
2020-10-11 00:52:18 +03:00
|
|
|
|
localAttributes: ObjectType = {}
|
2020-10-10 22:45:28 +03:00
|
|
|
|
localErrors: ValidationError[] = []
|
2020-10-11 00:52:18 +03:00
|
|
|
|
proxy: ObjectType = this.getInitialValue()
|
2020-10-10 22:45:28 +03:00
|
|
|
|
behavioralErrorVisibility: boolean = this.errorBehavior === 'live'
|
|
|
|
|
formShouldShowErrors: boolean = false
|
|
|
|
|
validationErrors: [] = []
|
2020-10-11 15:10:21 +03:00
|
|
|
|
pendingValidation: Promise<any> = Promise.resolve()
|
2020-10-10 22:45:28 +03:00
|
|
|
|
// These registries are used for injected messages registrants only (mostly internal).
|
|
|
|
|
ruleRegistry: [] = []
|
2020-10-11 00:52:18 +03:00
|
|
|
|
messageRegistry: ObjectType = {}
|
2020-10-10 22:45:28 +03:00
|
|
|
|
|
|
|
|
|
get context () {
|
|
|
|
|
return this.defineModel({
|
|
|
|
|
attributes: this.elementAttributes,
|
|
|
|
|
blurHandler: this.blurHandler.bind(this),
|
|
|
|
|
disableErrors: this.disableErrors,
|
|
|
|
|
errors: this.explicitErrors,
|
|
|
|
|
allErrors: this.allErrors,
|
|
|
|
|
formShouldShowErrors: this.formShouldShowErrors,
|
|
|
|
|
getValidationErrors: this.getValidationErrors.bind(this),
|
|
|
|
|
hasGivenName: this.hasGivenName,
|
|
|
|
|
hasValidationErrors: this.hasValidationErrors.bind(this),
|
|
|
|
|
help: this.help,
|
|
|
|
|
id: this.id || this.defaultId,
|
|
|
|
|
imageBehavior: this.imageBehavior,
|
|
|
|
|
limit: this.limit,
|
|
|
|
|
name: this.nameOrFallback,
|
|
|
|
|
performValidation: this.performValidation.bind(this),
|
|
|
|
|
preventWindowDrops: this.preventWindowDrops,
|
|
|
|
|
repeatable: this.repeatable,
|
|
|
|
|
setErrors: this.setErrors.bind(this),
|
|
|
|
|
showValidationErrors: this.showValidationErrors,
|
|
|
|
|
uploadBehavior: this.uploadBehavior,
|
|
|
|
|
uploadUrl: this.mergedUploadUrl,
|
|
|
|
|
uploader: this.uploader || this.$formulario.getUploader(),
|
|
|
|
|
validationErrors: this.validationErrors,
|
|
|
|
|
value: this.value,
|
|
|
|
|
visibleValidationErrors: this.visibleValidationErrors,
|
|
|
|
|
})
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
get parsedValidationRules () {
|
|
|
|
|
const parsedValidationRules = {}
|
|
|
|
|
Object.keys(this.validationRules).forEach(key => {
|
|
|
|
|
parsedValidationRules[snakeToCamel(key)] = this.validationRules[key]
|
|
|
|
|
})
|
|
|
|
|
return parsedValidationRules
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
get messages () {
|
|
|
|
|
const messages = {}
|
|
|
|
|
Object.keys(this.validationMessages).forEach((key) => {
|
|
|
|
|
messages[snakeToCamel(key)] = this.validationMessages[key]
|
|
|
|
|
})
|
|
|
|
|
Object.keys(this.messageRegistry).forEach((key) => {
|
|
|
|
|
messages[snakeToCamel(key)] = this.messageRegistry[key]
|
|
|
|
|
})
|
|
|
|
|
return messages
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Reducer for attributes that will be applied to each core input element.
|
|
|
|
|
*/
|
|
|
|
|
get elementAttributes () {
|
|
|
|
|
const attrs = Object.assign({}, this.localAttributes)
|
|
|
|
|
// pass the ID prop through to the root element
|
|
|
|
|
if (this.id) {
|
|
|
|
|
attrs.id = this.id
|
|
|
|
|
} else {
|
|
|
|
|
attrs.id = this.defaultId
|
|
|
|
|
}
|
|
|
|
|
// pass an explicitly given name prop through to the root element
|
|
|
|
|
if (this.hasGivenName) {
|
|
|
|
|
attrs.name = this.name
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
// If there is help text, have this element be described by it.
|
|
|
|
|
if (this.help) {
|
|
|
|
|
attrs['aria-describedby'] = `${attrs.id}-help`
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
return attrs
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Return the element’s name, or select a fallback.
|
|
|
|
|
*/
|
|
|
|
|
get nameOrFallback () {
|
|
|
|
|
return this.path !== '' ? `${this.path}.${this.name}` : this.name
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Determine if an input has a user-defined name.
|
|
|
|
|
*/
|
|
|
|
|
get hasGivenName () {
|
|
|
|
|
return typeof this.name !== 'boolean'
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* The validation label to use.
|
|
|
|
|
*/
|
|
|
|
|
get mergedValidationName () {
|
|
|
|
|
return this.validationName || this.name
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Use the uploadURL on the input if it exists, otherwise use the uploadURL
|
|
|
|
|
* that is defined as a plugin option.
|
|
|
|
|
*/
|
|
|
|
|
get mergedUploadUrl () {
|
|
|
|
|
return this.uploadUrl || this.$formulario.getUploadUrl()
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Does this computed property have errors
|
|
|
|
|
*/
|
|
|
|
|
get hasErrors () {
|
|
|
|
|
return this.allErrors.length > 0
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Returns if form has actively visible errors (of any kind)
|
|
|
|
|
*/
|
|
|
|
|
get hasVisibleErrors () {
|
|
|
|
|
return ((this.validationErrors && this.showValidationErrors) || !!this.explicitErrors.length)
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* The merged errors computed property.
|
|
|
|
|
* Each error is an object with fields message (translated message), rule (rule name) and context
|
|
|
|
|
*/
|
|
|
|
|
get allErrors (): ValidationError[] {
|
|
|
|
|
return [
|
|
|
|
|
...this.explicitErrors,
|
|
|
|
|
...arrayify(this.validationErrors)
|
|
|
|
|
]
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* All of the currently visible validation errors (does not include error handling)
|
|
|
|
|
*/
|
|
|
|
|
get visibleValidationErrors () {
|
|
|
|
|
return (this.showValidationErrors && this.validationErrors.length) ? this.validationErrors : []
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* These are errors we that have been explicity passed to us.
|
|
|
|
|
*/
|
|
|
|
|
get explicitErrors (): ValidationError[] {
|
|
|
|
|
return [
|
|
|
|
|
...arrayify(this.errors),
|
|
|
|
|
...this.localErrors,
|
|
|
|
|
...arrayify(this.error),
|
|
|
|
|
].map(message => ({ rule: null, context: null, message }))
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Determines if this formulario element is v-modeled or not.
|
|
|
|
|
*/
|
|
|
|
|
get isVmodeled (): boolean {
|
|
|
|
|
return !!(Object.prototype.hasOwnProperty.call(this.$options.propsData, 'formularioValue') &&
|
|
|
|
|
this._events &&
|
|
|
|
|
Array.isArray(this._events.input) &&
|
|
|
|
|
this._events.input.length)
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Determines if the field should show it's error (if it has one)
|
|
|
|
|
*/
|
|
|
|
|
get showValidationErrors (): boolean {
|
|
|
|
|
return this.showErrors || this.formShouldShowErrors || this.behavioralErrorVisibility
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
@Watch('$attrs', { deep: true })
|
|
|
|
|
onAttrsChanged (value) {
|
|
|
|
|
this.updateLocalAttributes(value)
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
@Watch('proxy')
|
|
|
|
|
onProxyChanged (newValue, oldValue) {
|
|
|
|
|
this.performValidation()
|
|
|
|
|
if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
|
|
|
|
|
this.context.model = newValue
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
2020-10-10 22:45:28 +03:00
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
@Watch('formularioValue')
|
|
|
|
|
onFormularioValueChanged (newValue, oldValue) {
|
|
|
|
|
if (this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
|
|
|
|
|
this.context.model = newValue
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
2020-10-10 22:45:28 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Watch('showValidationErrors', { immediate: true })
|
|
|
|
|
onShowValidationErrorsChanged (val) {
|
|
|
|
|
this.$emit('error-visibility', val)
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-05-22 14:22:56 +03:00
|
|
|
|
created () {
|
|
|
|
|
this.applyInitialValue()
|
|
|
|
|
if (this.formularioRegister && typeof this.formularioRegister === 'function') {
|
|
|
|
|
this.formularioRegister(this.nameOrFallback, this)
|
|
|
|
|
}
|
|
|
|
|
if (!this.disableErrors && typeof this.observeErrors === 'function') {
|
|
|
|
|
this.observeErrors({ callback: this.setErrors, type: 'input', field: this.nameOrFallback })
|
|
|
|
|
}
|
|
|
|
|
this.updateLocalAttributes(this.$attrs)
|
2020-10-10 22:45:28 +03:00
|
|
|
|
this.performValidation()
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-11 00:52:18 +03:00
|
|
|
|
// noinspection JSUnusedGlobalSymbols
|
2020-05-22 14:22:56 +03:00
|
|
|
|
beforeDestroy () {
|
|
|
|
|
if (!this.disableErrors && typeof this.removeErrorObserver === 'function') {
|
|
|
|
|
this.removeErrorObserver(this.setErrors)
|
|
|
|
|
}
|
|
|
|
|
if (typeof this.formularioDeregister === 'function') {
|
|
|
|
|
this.formularioDeregister(this.nameOrFallback)
|
|
|
|
|
}
|
2020-10-10 22:45:28 +03:00
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Defines the model used throughout the existing context.
|
|
|
|
|
* @param {object} context
|
|
|
|
|
*/
|
|
|
|
|
defineModel (context) {
|
|
|
|
|
return Object.defineProperty(context, 'model', {
|
|
|
|
|
get: this.modelGetter.bind(this),
|
|
|
|
|
set: this.modelSetter.bind(this),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the value from a model.
|
|
|
|
|
*/
|
|
|
|
|
modelGetter () {
|
|
|
|
|
const model = this.isVmodeled ? 'formularioValue' : 'proxy'
|
|
|
|
|
if (this[model] === undefined) {
|
2020-05-22 14:22:56 +03:00
|
|
|
|
return ''
|
2020-10-10 22:45:28 +03:00
|
|
|
|
}
|
|
|
|
|
return this[model]
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Set the value from a model.
|
|
|
|
|
*/
|
|
|
|
|
modelSetter (value) {
|
|
|
|
|
if (!shallowEqualObjects(value, this.proxy)) {
|
|
|
|
|
this.proxy = value
|
|
|
|
|
}
|
|
|
|
|
this.$emit('input', value)
|
|
|
|
|
if (this.context.name && typeof this.formularioSetter === 'function') {
|
|
|
|
|
this.formularioSetter(this.context.name, value)
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
/**
|
|
|
|
|
* Bound into the context object.
|
|
|
|
|
*/
|
2020-10-15 10:50:07 +03:00
|
|
|
|
blurHandler (): void {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
this.$emit('blur')
|
|
|
|
|
if (this.errorBehavior === 'blur') {
|
|
|
|
|
this.behavioralErrorVisibility = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
getInitialValue () {
|
2020-10-15 10:50:07 +03:00
|
|
|
|
if (has(this.$options.propsData as ObjectType, 'value')) {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
return this.value
|
2020-10-15 10:50:07 +03:00
|
|
|
|
} else if (has(this.$options.propsData as ObjectType, 'formularioValue')) {
|
2020-10-10 22:45:28 +03:00
|
|
|
|
return this.formularioValue
|
|
|
|
|
}
|
|
|
|
|
return ''
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
applyInitialValue () {
|
|
|
|
|
// This should only be run immediately on created and ensures that the
|
|
|
|
|
// proxy and the model are both the same before any additional registration.
|
|
|
|
|
if (!shallowEqualObjects(this.context.model, this.proxy)) {
|
|
|
|
|
this.context.model = this.proxy
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-22 14:22:56 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
updateLocalAttributes (value) {
|
|
|
|
|
if (!shallowEqualObjects(value, this.localAttributes)) {
|
|
|
|
|
this.localAttributes = value
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
performValidation () {
|
|
|
|
|
let rules = parseRules(this.validation, this.$formulario.rules(this.parsedValidationRules))
|
|
|
|
|
// Add in ruleRegistry rules. These are added directly via injection from
|
|
|
|
|
// children and not part of the standard validation rule set.
|
|
|
|
|
rules = this.ruleRegistry.length ? this.ruleRegistry.concat(rules) : rules
|
|
|
|
|
this.pendingValidation = this.runRules(rules)
|
|
|
|
|
.then(messages => this.didValidate(messages))
|
|
|
|
|
return this.pendingValidation
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
runRules (rules) {
|
|
|
|
|
const run = ([rule, args, ruleName]) => {
|
|
|
|
|
let res = rule({
|
2020-05-22 14:22:56 +03:00
|
|
|
|
value: this.context.model,
|
2020-10-10 22:45:28 +03:00
|
|
|
|
getFormValues: this.getFormValues.bind(this),
|
|
|
|
|
name: this.context.name
|
|
|
|
|
}, ...args)
|
|
|
|
|
res = (res instanceof Promise) ? res : Promise.resolve(res)
|
|
|
|
|
return res.then(result => result ? false : this.getMessageObject(ruleName, args))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Promise(resolve => {
|
|
|
|
|
const resolveGroups = (groups, allMessages = []) => {
|
|
|
|
|
const ruleGroup = groups.shift()
|
|
|
|
|
if (Array.isArray(ruleGroup) && ruleGroup.length) {
|
|
|
|
|
Promise.all(ruleGroup.map(run))
|
|
|
|
|
.then(messages => messages.filter(m => !!m))
|
|
|
|
|
.then(messages => {
|
|
|
|
|
messages = Array.isArray(messages) ? messages : []
|
|
|
|
|
// The rule passed or its a non-bailing group, and there are additional groups to check, continue
|
|
|
|
|
if ((!messages.length || !ruleGroup.bail) && groups.length) {
|
|
|
|
|
return resolveGroups(groups, allMessages.concat(messages))
|
|
|
|
|
}
|
|
|
|
|
return resolve(allMessages.concat(messages))
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
resolve([])
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
}
|
2020-10-10 22:45:28 +03:00
|
|
|
|
resolveGroups(groupBails(rules))
|
|
|
|
|
})
|
|
|
|
|
}
|
2020-05-25 12:49:49 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
didValidate (messages) {
|
|
|
|
|
const validationChanged = !shallowEqualObjects(messages, this.validationErrors)
|
|
|
|
|
this.validationErrors = messages
|
|
|
|
|
if (validationChanged) {
|
|
|
|
|
const errorObject = this.getErrorObject()
|
|
|
|
|
this.$emit('validation', errorObject)
|
|
|
|
|
if (this.formularioFieldValidation && typeof this.formularioFieldValidation === 'function') {
|
|
|
|
|
this.formularioFieldValidation(errorObject)
|
2020-05-25 12:49:49 +03:00
|
|
|
|
}
|
2020-10-10 22:45:28 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getMessageObject (ruleName, args) {
|
|
|
|
|
const context = {
|
|
|
|
|
args,
|
|
|
|
|
name: this.mergedValidationName,
|
|
|
|
|
value: this.context.model,
|
|
|
|
|
vm: this,
|
|
|
|
|
formValues: this.getFormValues()
|
|
|
|
|
}
|
|
|
|
|
const message = this.getMessageFunc(ruleName)(context)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
rule: ruleName,
|
|
|
|
|
context,
|
|
|
|
|
message,
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
getMessageFunc (ruleName) {
|
|
|
|
|
ruleName = snakeToCamel(ruleName)
|
|
|
|
|
if (this.messages && typeof this.messages[ruleName] !== 'undefined') {
|
|
|
|
|
switch (typeof this.messages[ruleName]) {
|
2020-10-09 22:58:28 +03:00
|
|
|
|
case 'function':
|
|
|
|
|
return this.messages[ruleName]
|
|
|
|
|
case 'string':
|
|
|
|
|
case 'boolean':
|
|
|
|
|
return () => this.messages[ruleName]
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
2020-10-10 22:45:28 +03:00
|
|
|
|
}
|
|
|
|
|
return (context) => this.$formulario.validationMessage(ruleName, context, this)
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
hasValidationErrors () {
|
|
|
|
|
return new Promise(resolve => {
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
this.pendingValidation.then(() => resolve(!!this.validationErrors.length))
|
2020-05-22 14:22:56 +03:00
|
|
|
|
})
|
2020-10-10 22:45:28 +03:00
|
|
|
|
})
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
getValidationErrors () {
|
|
|
|
|
return new Promise(resolve => {
|
|
|
|
|
this.$nextTick(() => this.pendingValidation.then(() => resolve(this.getErrorObject())))
|
|
|
|
|
})
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
getErrorObject () {
|
|
|
|
|
return {
|
|
|
|
|
name: this.context.nameOrFallback || this.context.name,
|
|
|
|
|
errors: this.validationErrors.filter(s => typeof s === 'object'),
|
|
|
|
|
hasErrors: !!this.validationErrors.length
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
setErrors (errors) {
|
|
|
|
|
this.localErrors = arrayify(errors)
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
registerRule (rule, args, ruleName, message = null) {
|
|
|
|
|
if (!this.ruleRegistry.some(r => r[2] === ruleName)) {
|
|
|
|
|
// These are the raw rule format since they will be used directly.
|
|
|
|
|
this.ruleRegistry.push([rule, args, ruleName])
|
|
|
|
|
if (message !== null) {
|
|
|
|
|
this.messageRegistry[ruleName] = message
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
2020-10-10 22:45:28 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-09 22:58:28 +03:00
|
|
|
|
|
2020-10-10 22:45:28 +03:00
|
|
|
|
removeRule (key) {
|
|
|
|
|
const ruleIndex = this.ruleRegistry.findIndex(r => r[2] === key)
|
|
|
|
|
if (ruleIndex >= 0) {
|
|
|
|
|
this.ruleRegistry.splice(ruleIndex, 1)
|
|
|
|
|
delete this.messageRegistry[key]
|
2020-05-22 14:22:56 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|