1
0
mirror of synced 2024-11-29 16:46:09 +03:00
vue-formulario/src/components/FormulateElement.vue

314 lines
8.1 KiB
Vue
Raw Normal View History

2018-01-31 01:21:21 +03:00
<template>
2018-02-01 01:20:29 +03:00
<div :class="classes">
<div
class="formulate-element-input-wrapper"
:data-type="type"
:data-type-classification="classification"
>
2018-01-31 01:21:21 +03:00
<!-- TEXT STYLE INPUTS -->
<label
:for="id"
v-text="label"
2018-02-01 01:20:29 +03:00
v-if="label && (!isBoxInput || optionList.length > 1)"
2018-01-31 01:21:21 +03:00
/>
<input
ref="input"
:type="type"
:name="name"
v-model="val"
2018-02-01 01:20:29 +03:00
v-bind="attributes"
2018-01-31 01:21:21 +03:00
v-if="isTextInput"
2018-02-01 01:20:29 +03:00
@blur="errorBlurState = true"
2018-01-31 01:21:21 +03:00
>
<!-- BUTTON INPUTS -->
<button
:type="type"
v-text="label || name"
v-if="isButtonInput"
2018-02-01 01:20:29 +03:00
:disabled="type === 'submit' && (form.hasErrors && form.behavior === 'live')"
2018-01-31 01:21:21 +03:00
/>
<!-- SELECT INPUTS -->
2018-02-01 01:20:29 +03:00
<select
v-bind="attributes"
v-if="isSelectInput"
:name="name"
v-model="val"
@blur="errorBlurState = true"
>
<option
v-for="option in optionList"
:value="option.value"
:key="option.id"
v-bind="option.attributes || {}"
2018-02-01 01:20:29 +03:00
v-text="option.label"
/>
</select>
<!-- BOX INPUTS -->
<div
class="formulate-element-box-input-group"
v-if="isBoxInput"
>
<template v-for="option in optionList">
<input
type="radio"
:name="name"
:id="option.id"
:value="option.value"
:key="`${option.id}-input`"
v-bind="attributes"
v-model="val"
v-if="type === 'radio'"
@blur="errorBlurState = true"
>
<input
type="checkbox"
:name="name"
:id="option.id"
:value="option.value"
:key="`${option.id}-input`"
v-bind="attributes"
v-model="val"
v-if="type === 'checkbox'"
@blur="errorBlurState = true"
>
<label
:for="option.id"
:key="`${option.id}-label`"
v-text="option.label"
/>
</template>
</div>
2018-01-31 01:21:21 +03:00
<!-- CUSTOM SLOT INPUTS -->
<slot v-if="hasCustomInput" />
2018-02-01 01:20:29 +03:00
<!-- UNSUPORTED INPUT -->
<div
style="background-color: red; color: white"
v-if="isUnsupportedInput"
v-text="`Unsupported field type: “${type}”.`"
/>
2018-01-31 01:21:21 +03:00
</div>
<ul
class="formulate-errors"
2018-02-01 01:20:29 +03:00
v-if="shouldShowErrors && localAndValidationErrors.length"
2018-01-31 01:21:21 +03:00
>
<li
v-for="error in localAndValidationErrors"
v-text="error"
:key="error"
/>
</ul>
</div>
</template>
<script>
import {inputTypes, equals, reduce, filter} from '../utils'
2018-01-31 01:21:21 +03:00
import shortid from 'shortid'
export default {
props: {
type: {
type: [String, Boolean],
default: 'text'
},
name: {
type: String,
required: true
},
initial: {
type: [String, Number, Boolean],
2018-01-31 01:21:21 +03:00
default: false
},
validation: {
type: [String, Boolean],
default: false
},
errors: {
type: Array,
default: () => []
},
label: {
type: [String, Boolean],
default: false
},
id: {
type: [String],
default: () => shortid.generate()
2018-02-01 01:20:29 +03:00
},
min: {
type: [String, Number, Boolean],
default: () => false
},
max: {
type: [String, Number, Boolean],
default: () => false
},
placeholder: {
type: [String, Number, Boolean],
default: () => false
},
options: {
type: [Object, Array],
default: () => []
},
multiple: {
type: Boolean,
default: false
},
showErrors: {
type: [Object, Boolean],
default: () => ({})
},
validationLabel: {
type: [String, Boolean],
default: false
2018-02-01 01:20:29 +03:00
}
},
data () {
return {
errorBlurState: false
2018-01-31 01:21:21 +03:00
}
},
computed: {
classification () {
if (this.isBoxInput) return 'box'
if (this.isButtonInput) return 'button'
if (this.isSelectInput) return 'select'
if (this.hasCustomInput) return 'custom'
return 'unsupported'
},
2018-01-31 01:21:21 +03:00
hasCustomInput () {
return (this.$slots.default && this.$slots.default.length)
},
isTextInput () {
return !this.hasCustomInput && inputTypes.text.includes(this.type)
},
isButtonInput () {
return !this.hasCustomInput && inputTypes.button.includes(this.type)
},
2018-02-01 01:20:29 +03:00
isSelectInput () {
return !this.hasCustomInput && inputTypes.select.includes(this.type)
},
isBoxInput () {
return !this.hasCustomInput && inputTypes.box.includes(this.type)
},
isUnsupportedInput () {
return (!this.hasCustomInput && !this.isTextInput && !this.isButtonInput && !this.isSelectInput && !this.isBoxInput)
2018-01-31 01:21:21 +03:00
},
form () {
let parent = this.$parent
while (parent && parent.$data && parent.$data.parentIdentifier !== 'vue-formulate-wrapper-element') {
parent = parent.$parent
}
if (!parent.$data || parent.$data.parentIdentifier !== 'vue-formulate-wrapper-element') {
throw new Error('FormulateElement has no FormulateWrapper element')
}
return parent
},
values () {
return this.form.values
},
value () {
2018-02-01 01:20:29 +03:00
let value = this.values[this.name]
if (value === undefined) {
switch (this.type) {
case 'color':
return '#000000'
2018-02-01 01:20:29 +03:00
case 'checkbox':
if (this.optionList.length > 1) {
return []
2018-02-01 01:20:29 +03:00
}
break
}
}
return value
2018-01-31 01:21:21 +03:00
},
module () {
return this.form.$props['module']
},
formName () {
return this.form.$props['name']
},
2018-02-01 01:20:29 +03:00
classes () {
return {
'formulate-element': true,
[`formulate-element--type--${this.type}`]: true,
2018-02-01 01:20:29 +03:00
'formulate-element--has-value': !!this.value,
'formulate-element--has-errors': this.localAndValidationErrors.length && this.shouldShowErrors
}
},
2018-01-31 01:21:21 +03:00
validationErrors () {
return this.form.validationErrors[this.name] || []
},
storeErrors () {
return this.form.storeErrors[this.name] || []
},
formErrors () {
2018-01-31 01:21:21 +03:00
return this.form.errors[this.name] || []
},
localAndValidationErrors () {
return this.errors.concat(this.validationErrors).concat(this.formErrors)
2018-01-31 01:21:21 +03:00
},
2018-02-01 01:20:29 +03:00
shouldShowErrors () {
let show = this.form.shouldShowErrors
if (this.form.behavior === 'blur') {
show = show || this.errorBlurState
2018-02-01 01:20:29 +03:00
}
if (this.showErrors === false || this.showErrors === true) {
show = this.showErrors
}
return show
},
2018-01-31 01:21:21 +03:00
attributes () {
2018-02-01 01:20:29 +03:00
return ['min', 'max', 'placeholder', 'id', 'multiple']
.filter(prop => this[prop] !== false)
.reduce((attributes, attr) => {
attributes[attr] = this[attr]
return attributes
}, {})
},
optionList () {
if (!Array.isArray(this.options)) {
return reduce(this.options, (options, value, label) => options.concat({value, label, id: shortid.generate()}), [])
} else if (Array.isArray(this.options) && !this.options.length) {
return [{value: this.name, label: (this.label || this.name), id: shortid.generate()}]
}
return this.options
2018-01-31 01:21:21 +03:00
},
val: {
set (value) {
this.form.update({field: this.name, value})
2018-02-01 01:20:29 +03:00
if (this.isTextInput) {
this.$refs.input.value = value
}
2018-01-31 01:21:21 +03:00
},
get () {
return this.value
}
}
},
watch: {
2018-02-01 01:20:29 +03:00
localAndValidationErrors () {
if (!equals(this.localAndValidationErrors, this.storeErrors)) {
this.form.updateFieldErrors({
field: this.name,
errors: this.localAndValidationErrors
})
}
},
initial () {
this.form.update({field: this.name, value: this.initial})
2018-01-31 01:21:21 +03:00
}
},
created () {
this.form.registerField(
this.name,
filter(this.$props, (prop, value) => ['name', 'type', 'id', 'label', 'validation', 'validationLabel'].includes(prop))
)
2018-01-31 01:21:21 +03:00
if (this.initial !== false) {
this.form.setInitial(this.name, this.initial)
2018-01-31 01:21:21 +03:00
}
}
}
</script>