1
0
mirror of synced 2024-11-26 15:26:02 +03:00
vue-formulario/src/FormulateInput.vue

308 lines
8.2 KiB
Vue

<template>
<div
class="formulate-input"
:data-classification="classification"
:data-has-errors="hasErrors"
:data-is-showing-errors="hasErrors && showFieldErrors"
:data-type="type"
>
<div class="formulate-input-wrapper">
<slot
v-if="context.hasLabel && context.labelPosition === 'before'"
name="label"
v-bind="context"
>
<label
class="formulate-input-label formulate-input-label--before"
:for="context.attributes.id"
v-text="context.label"
/>
</slot>
<slot
name="element"
v-bind="context"
>
<component
:is="context.component"
:context="context"
>
<slot v-bind="context" />
</component>
</slot>
<slot
v-if="context.hasLabel && context.labelPosition === 'after'"
name="label"
v-bind="context.label"
>
<label
class="formulate-input-label formulate-input-label--after"
:for="context.attributes.id"
v-text="context.label"
/>
</slot>
</div>
<div
v-if="help"
class="formulate-input-help"
v-text="help"
/>
<FormulateInputErrors
v-if="showFieldErrors"
:errors="mergedErrors"
/>
</div>
</template>
<script>
import context from './libs/context'
import { shallowEqualObjects, parseRules } from './libs/utils'
import nanoid from 'nanoid/non-secure'
export default {
name: 'FormulateInput',
inheritAttrs: false,
inject: {
formulateFormSetter: { default: undefined },
formulateFormRegister: { default: undefined },
getFormValues: { default: () => () => ({}) }
},
model: {
prop: 'formulateValue',
event: 'input'
},
props: {
type: {
type: String,
default: 'text'
},
name: {
type: [String, Boolean],
default: true
},
/* eslint-disable */
formulateValue: {
default: ''
},
value: {
default: false
},
/* eslint-enable */
options: {
type: [Object, Array, Boolean],
default: false
},
optionGroups: {
type: [Object, Boolean],
default: false
},
id: {
type: [String, Boolean, Number],
default: false
},
label: {
type: [String, Boolean],
default: false
},
labelPosition: {
type: [String, Boolean],
default: false
},
help: {
type: [String, Boolean],
default: false
},
debug: {
type: Boolean,
default: false
},
errors: {
type: [String, Array, Boolean],
default: false
},
validation: {
type: [String, Boolean, Array],
default: false
},
validationName: {
type: [String, Boolean],
default: false
},
error: {
type: [String, Boolean],
default: false
},
errorBehavior: {
type: String,
default: 'blur',
validator: function (value) {
return ['blur', 'live'].includes(value)
}
},
showErrors: {
type: Boolean,
default: false
},
imageBehavior: {
type: String,
default: 'preview'
},
uploadUrl: {
type: [String, Boolean],
default: false
},
uploader: {
type: [Function, Object, Boolean],
default: false
},
uploadBehavior: {
type: String,
default: 'live'
},
preventWindowDrops: {
type: Boolean,
default: true
},
showValue: {
type: [String, Boolean],
default: false
},
validationMessages: {
type: Object,
default: () => ({})
},
validationRules: {
type: Object,
default: () => ({})
},
checked: {
type: [String, Boolean],
default: false
}
},
data () {
return {
defaultId: nanoid(9),
localAttributes: {},
internalModelProxy: this.getInitialValue(),
behavioralErrorVisibility: (this.errorBehavior === 'live'),
formShouldShowErrors: false,
validationErrors: [],
pendingValidation: Promise.resolve()
}
},
computed: {
...context,
classification () {
const classification = this.$formulate.classify(this.type)
return (classification === 'box' && this.options) ? 'group' : classification
},
component () {
return (this.classification === 'group') ? 'FormulateInputGroup' : this.$formulate.component(this.type)
}
},
watch: {
'$attrs': {
handler (value) {
this.updateLocalAttributes(value)
},
deep: true
},
internalModelProxy (newValue, oldValue) {
this.performValidation()
if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
this.context.model = newValue
}
},
formulateValue (newValue, oldValue) {
if (this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
this.context.model = newValue
}
}
},
created () {
this.applyInitialValue()
if (this.formulateFormRegister && typeof this.formulateFormRegister === 'function') {
this.formulateFormRegister(this.nameOrFallback, this)
}
this.updateLocalAttributes(this.$attrs)
this.performValidation()
},
methods: {
getInitialValue () {
// Manually request classification, pre-computed props
var classification = this.$formulate.classify(this.type)
classification = (classification === 'box' && this.options) ? 'group' : classification
if (classification === 'box' && this.checked) {
return this.value || true
} else if (Object.prototype.hasOwnProperty.call(this.$options.propsData, 'value') && classification !== 'box') {
return this.value
} else if (Object.prototype.hasOwnProperty.call(this.$options.propsData, 'formulateValue')) {
return this.formulateValue
}
return ''
},
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.internalModelProxy) &&
// we dont' want to set the model if we are a sub-box of a multi-box field
(Object.prototype.hasOwnProperty(this.$options.propsData, 'options') && this.classification === 'box')
) {
this.context.model = this.internalModelProxy
}
},
updateLocalAttributes (value) {
if (!shallowEqualObjects(value, this.localAttributes)) {
this.localAttributes = value
}
},
performValidation () {
const rules = parseRules(this.validation, this.$formulate.rules(this.validationRules))
this.pendingValidation = Promise.all(
rules.map(([rule, args]) => {
var res = rule({
value: this.context.model,
getFormValues: this.getFormValues.bind(this),
name: this.context.name
}, ...args)
res = (res instanceof Promise) ? res : Promise.resolve(res)
return res.then(res => res ? false : this.getValidationMessage(rule, args))
})
)
.then(result => result.filter(result => result))
.then(errorMessages => { this.validationErrors = errorMessages })
return this.pendingValidation
},
getValidationMessage (rule, args) {
return this.getValidationFunction(rule)({
args,
name: this.mergedValidationName,
value: this.context.model,
vm: this,
formValues: this.getFormValues()
})
},
getValidationFunction (rule) {
const ruleName = rule.name.substr(0, 1) === '_' ? rule.name.substr(1) : rule.name
if (this.validationMessages && typeof this.validationMessages === 'object' && typeof this.validationMessages[ruleName] !== 'undefined') {
switch (typeof this.validationMessages[ruleName]) {
case 'function':
return this.validationMessages[ruleName]
case 'string':
return () => this.validationMessages[ruleName]
}
}
return (context) => this.$formulate.validationMessage(rule.name, context)
},
hasValidationErrors () {
return new Promise(resolve => {
this.$nextTick(() => {
this.pendingValidation.then(() => resolve(!!this.validationErrors.length))
})
})
}
}
}
</script>