2018-01-31 01:21:21 +03:00
|
|
|
<template>
|
2018-02-01 01:20:29 +03:00
|
|
|
<div :class="classes">
|
2019-02-26 18:54:16 +03:00
|
|
|
<slot name="prefix" />
|
2018-02-27 06:15:15 +03:00
|
|
|
<div
|
|
|
|
class="formulate-element-input-wrapper"
|
|
|
|
:data-type="type"
|
2018-02-27 06:25:58 +03:00
|
|
|
:data-classification="classification"
|
2018-04-09 18:51:14 +03:00
|
|
|
:data-is-disabled="disabled"
|
2018-02-27 06:15:15 +03:00
|
|
|
>
|
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"
|
2018-03-19 16:58:45 +03:00
|
|
|
:class="elementClasses"
|
2018-01-31 01:21:21 +03:00
|
|
|
: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"
|
2019-03-18 20:48:26 +03:00
|
|
|
@blur="setBlurState"
|
|
|
|
@focus="setFocusState"
|
2018-04-09 18:51:14 +03:00
|
|
|
:disabled="disabled"
|
2018-10-16 06:21:31 +03:00
|
|
|
:step="step"
|
2018-01-31 01:21:21 +03:00
|
|
|
>
|
2018-03-30 21:51:47 +03:00
|
|
|
<textarea
|
|
|
|
ref="textarea"
|
|
|
|
:class="elementClasses"
|
|
|
|
:type="type"
|
|
|
|
:name="name"
|
|
|
|
v-model="val"
|
|
|
|
v-bind="attributes"
|
|
|
|
v-if="isTextareaInput"
|
2019-03-18 20:48:26 +03:00
|
|
|
@blur="setBlurState"
|
|
|
|
@focus="setFocusState"
|
2018-04-09 18:51:14 +03:00
|
|
|
:disabled="disabled"
|
2018-03-30 21:51:47 +03:00
|
|
|
/>
|
2018-01-31 01:21:21 +03:00
|
|
|
<!-- BUTTON INPUTS -->
|
|
|
|
<button
|
|
|
|
:type="type"
|
2018-03-19 16:58:45 +03:00
|
|
|
:class="elementClasses"
|
2018-01-31 01:21:21 +03:00
|
|
|
v-if="isButtonInput"
|
2018-04-09 18:51:14 +03:00
|
|
|
:disabled="disabled || (type === 'submit' && (form.hasErrors && form.behavior === 'live'))"
|
|
|
|
>
|
|
|
|
<slot
|
|
|
|
v-if="$slots.button"
|
|
|
|
name="button"
|
|
|
|
/>
|
|
|
|
<span
|
|
|
|
v-text="label || name"
|
|
|
|
v-else
|
|
|
|
/>
|
|
|
|
</button>
|
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"
|
2018-03-19 16:58:45 +03:00
|
|
|
:class="elementClasses"
|
2018-02-01 01:20:29 +03:00
|
|
|
:name="name"
|
|
|
|
v-model="val"
|
2019-03-18 20:48:26 +03:00
|
|
|
@blur="setBlurState"
|
|
|
|
@focus="setFocusState"
|
2018-04-09 18:51:14 +03:00
|
|
|
:disabled="disabled"
|
2018-02-01 01:20:29 +03:00
|
|
|
>
|
2019-09-24 18:28:15 +03:00
|
|
|
<template
|
|
|
|
v-if="!optionGroups"
|
|
|
|
>
|
|
|
|
<option
|
|
|
|
v-for="option in optionList"
|
|
|
|
:value="option.value"
|
|
|
|
:key="option.id"
|
|
|
|
v-bind="option.attributes || {}"
|
|
|
|
v-text="option.label"
|
|
|
|
/>
|
|
|
|
</template>
|
|
|
|
<template
|
|
|
|
v-else
|
|
|
|
>
|
|
|
|
<optgroup
|
|
|
|
v-for="(list, label) in optionGroups"
|
|
|
|
:key="label"
|
|
|
|
:label="label"
|
|
|
|
>
|
|
|
|
<option
|
|
|
|
v-for="option in createOptionList(list)"
|
|
|
|
:value="option.value"
|
|
|
|
:key="option.value"
|
|
|
|
v-bind="option.attributes || {}"
|
|
|
|
v-text="option.label"
|
|
|
|
/>
|
|
|
|
</optgroup>
|
|
|
|
</template>
|
2018-02-01 01:20:29 +03:00
|
|
|
</select>
|
|
|
|
<!-- BOX INPUTS -->
|
|
|
|
<div
|
2018-05-19 06:47:14 +03:00
|
|
|
class="formulate-element-box-input-wrap"
|
2018-02-01 01:20:29 +03:00
|
|
|
v-if="isBoxInput"
|
|
|
|
>
|
2018-05-19 06:47:14 +03:00
|
|
|
<div
|
|
|
|
class="formulate-element-box-input-group"
|
|
|
|
v-for="option in optionList"
|
|
|
|
:key="option.id"
|
|
|
|
>
|
2018-02-01 01:20:29 +03:00
|
|
|
<input
|
|
|
|
type="radio"
|
2018-03-19 16:58:45 +03:00
|
|
|
:class="elementClasses"
|
2018-02-01 01:20:29 +03:00
|
|
|
:name="name"
|
|
|
|
:id="option.id"
|
|
|
|
:value="option.value"
|
|
|
|
:key="`${option.id}-input`"
|
|
|
|
v-bind="attributes"
|
|
|
|
v-model="val"
|
|
|
|
v-if="type === 'radio'"
|
2019-03-18 20:48:26 +03:00
|
|
|
@blur="setBlurState"
|
|
|
|
@focus="setFocusState"
|
2019-09-23 23:47:02 +03:00
|
|
|
:disabled="disabled || option.disabled"
|
2018-02-01 01:20:29 +03:00
|
|
|
>
|
|
|
|
<input
|
|
|
|
type="checkbox"
|
2018-03-19 16:58:45 +03:00
|
|
|
:class="elementClasses"
|
2018-02-01 01:20:29 +03:00
|
|
|
:name="name"
|
|
|
|
:id="option.id"
|
|
|
|
:value="option.value"
|
|
|
|
:key="`${option.id}-input`"
|
|
|
|
v-bind="attributes"
|
|
|
|
v-model="val"
|
|
|
|
v-if="type === 'checkbox'"
|
2019-03-18 20:48:26 +03:00
|
|
|
@blur="setBlurState"
|
|
|
|
@focus="setFocusState"
|
2019-09-23 23:47:02 +03:00
|
|
|
:disabled="disabled || option.disabled"
|
2018-02-01 01:20:29 +03:00
|
|
|
>
|
|
|
|
<label
|
|
|
|
:for="option.id"
|
|
|
|
:key="`${option.id}-label`"
|
|
|
|
v-text="option.label"
|
|
|
|
/>
|
2018-05-19 06:47:14 +03:00
|
|
|
</div>
|
2018-02-01 01:20:29 +03:00
|
|
|
</div>
|
2018-01-31 01:21:21 +03:00
|
|
|
<!-- CUSTOM SLOT INPUTS -->
|
|
|
|
<slot v-if="hasCustomInput" />
|
2018-02-01 01:20:29 +03:00
|
|
|
|
2019-03-18 20:48:26 +03:00
|
|
|
<!-- UNSUPPORTED INPUT -->
|
2018-02-01 01:20:29 +03:00
|
|
|
<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>
|
2018-05-18 21:46:04 +03:00
|
|
|
<div
|
|
|
|
class="formulate-help"
|
|
|
|
v-if="help"
|
|
|
|
v-text="help"
|
|
|
|
/>
|
2018-02-27 18:18:31 +03:00
|
|
|
<transition
|
2018-02-27 17:04:07 +03:00
|
|
|
name="formulate-errors"
|
2018-01-31 01:21:21 +03:00
|
|
|
>
|
2018-02-27 18:18:31 +03:00
|
|
|
<transition-group
|
|
|
|
tag="ul"
|
|
|
|
name="formulate-error-list"
|
|
|
|
class="formulate-errors"
|
|
|
|
v-if="shouldShowErrors && localAndValidationErrors.length"
|
|
|
|
>
|
|
|
|
<li
|
|
|
|
v-for="error in localAndValidationErrors"
|
|
|
|
v-text="error"
|
|
|
|
:key="error"
|
|
|
|
/>
|
|
|
|
</transition-group>
|
|
|
|
</transition>
|
2019-02-26 18:54:16 +03:00
|
|
|
<slot name="suffix" />
|
2018-01-31 01:21:21 +03:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
2018-02-02 20:07:51 +03:00
|
|
|
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: {
|
2018-02-16 07:30:06 +03:00
|
|
|
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
|
|
|
|
},
|
2018-08-09 06:30:16 +03:00
|
|
|
maxlength: {
|
|
|
|
type: [String, Number, Boolean],
|
|
|
|
default: () => false
|
|
|
|
},
|
2018-11-20 19:51:32 +03:00
|
|
|
pattern: {
|
|
|
|
type: [String, Number, Boolean],
|
|
|
|
default: () => false
|
|
|
|
},
|
2018-08-09 06:30:16 +03:00
|
|
|
minlength: {
|
|
|
|
type: [String, Number, Boolean],
|
|
|
|
default: () => false
|
|
|
|
},
|
2018-02-01 01:20:29 +03:00
|
|
|
placeholder: {
|
|
|
|
type: [String, Number, Boolean],
|
|
|
|
default: () => false
|
|
|
|
},
|
2018-10-16 06:21:31 +03:00
|
|
|
step: {
|
|
|
|
type: [String, Number, Boolean],
|
|
|
|
default: () => false
|
|
|
|
},
|
2018-02-01 01:20:29 +03:00
|
|
|
options: {
|
|
|
|
type: [Object, Array],
|
|
|
|
default: () => []
|
|
|
|
},
|
2019-09-24 18:28:15 +03:00
|
|
|
optionGroups: {
|
|
|
|
type: [Boolean, Object],
|
|
|
|
default: false,
|
|
|
|
validator: function (value) {
|
|
|
|
if (value === false) {
|
|
|
|
return true
|
|
|
|
} else if (typeof value === 'boolean') {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
},
|
2018-02-01 01:20:29 +03:00
|
|
|
multiple: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false
|
|
|
|
},
|
|
|
|
showErrors: {
|
|
|
|
type: [Object, Boolean],
|
|
|
|
default: () => ({})
|
2018-02-02 21:38:05 +03:00
|
|
|
},
|
|
|
|
validationLabel: {
|
|
|
|
type: [String, Boolean],
|
|
|
|
default: false
|
2018-03-19 16:58:45 +03:00
|
|
|
},
|
|
|
|
elementClasses: {
|
|
|
|
type: [String, Array, Object],
|
|
|
|
default: () => {}
|
2018-04-09 18:51:14 +03:00
|
|
|
},
|
|
|
|
disabled: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false
|
2018-05-18 21:46:04 +03:00
|
|
|
},
|
|
|
|
help: {
|
|
|
|
type: [Boolean, String],
|
|
|
|
default: false
|
2018-02-01 01:20:29 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
data () {
|
|
|
|
return {
|
2019-03-18 20:48:26 +03:00
|
|
|
errorBlurState: false,
|
|
|
|
focusState: false
|
2018-01-31 01:21:21 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
2018-02-27 06:23:34 +03:00
|
|
|
classification () {
|
2018-02-27 06:31:59 +03:00
|
|
|
if (this.isTextInput) return 'text'
|
2019-09-10 08:08:04 +03:00
|
|
|
if (this.isTextareaInput) return 'textarea'
|
2018-02-27 06:23:34 +03:00
|
|
|
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)
|
|
|
|
},
|
2018-03-30 21:51:47 +03:00
|
|
|
isTextareaInput () {
|
|
|
|
return !this.hasCustomInput && inputTypes.textarea.includes(this.type)
|
|
|
|
},
|
2018-01-31 01:21:21 +03:00
|
|
|
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 () {
|
2018-03-30 21:56:41 +03:00
|
|
|
return (!this.hasCustomInput && !this.isTextInput && !this.isButtonInput && !this.isSelectInput && !this.isBoxInput && !this.isTextareaInput)
|
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':
|
2018-02-02 20:07:51 +03:00
|
|
|
return '#000000'
|
2018-02-01 01:20:29 +03:00
|
|
|
case 'checkbox':
|
|
|
|
if (this.optionList.length > 1) {
|
2018-02-02 20:07:51 +03:00
|
|
|
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,
|
2018-02-27 06:15:15 +03:00
|
|
|
[`formulate-element--type--${this.type}`]: true,
|
2018-02-01 01:20:29 +03:00
|
|
|
'formulate-element--has-value': !!this.value,
|
2019-02-26 18:54:16 +03:00
|
|
|
'formulate-element--has-errors': this.localAndValidationErrors.length && this.shouldShowErrors,
|
2019-03-18 20:48:26 +03:00
|
|
|
'formulate-element--has-prefix': !!this.$slots.prefix,
|
|
|
|
'formulate-element--has-suffix': !!this.$slots.suffix,
|
|
|
|
'formulate-element--has-focus': !!this.focusState
|
2018-02-01 01:20:29 +03:00
|
|
|
}
|
|
|
|
},
|
2018-01-31 01:21:21 +03:00
|
|
|
validationErrors () {
|
|
|
|
return this.form.validationErrors[this.name] || []
|
|
|
|
},
|
|
|
|
storeErrors () {
|
2018-02-16 07:21:29 +03:00
|
|
|
return this.form.storeErrors[this.name] || []
|
|
|
|
},
|
|
|
|
formErrors () {
|
2018-01-31 01:21:21 +03:00
|
|
|
return this.form.errors[this.name] || []
|
|
|
|
},
|
|
|
|
localAndValidationErrors () {
|
2018-02-16 07:21:29 +03:00
|
|
|
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') {
|
2018-02-01 19:02:58 +03:00
|
|
|
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-11-20 19:51:32 +03:00
|
|
|
return ['min', 'max', 'minlength', 'maxlength', 'placeholder', 'id', 'multiple', 'pattern']
|
2018-02-01 01:20:29 +03:00
|
|
|
.filter(prop => this[prop] !== false)
|
|
|
|
.reduce((attributes, attr) => {
|
|
|
|
attributes[attr] = this[attr]
|
|
|
|
return attributes
|
|
|
|
}, {})
|
|
|
|
},
|
|
|
|
optionList () {
|
2019-09-24 18:28:15 +03:00
|
|
|
return this.createOptionList(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
|
|
|
|
})
|
|
|
|
}
|
2018-02-05 22:17:26 +03:00
|
|
|
},
|
|
|
|
initial () {
|
|
|
|
this.form.update({field: this.name, value: this.initial})
|
2018-01-31 01:21:21 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
created () {
|
2018-03-07 23:44:35 +03:00
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
this.register()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
mounted () {
|
|
|
|
this.register()
|
|
|
|
},
|
|
|
|
beforeDestroy () {
|
|
|
|
this.form.deregisterField(this.name)
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
register () {
|
|
|
|
this.form.registerField(
|
|
|
|
this.name,
|
|
|
|
filter(this.$props, (prop, value) => ['name', 'type', 'id', 'label', 'validation', 'validationLabel'].includes(prop))
|
|
|
|
)
|
|
|
|
if (this.initial !== false) {
|
|
|
|
this.form.setInitial(this.name, this.initial)
|
|
|
|
}
|
2019-03-18 20:48:26 +03:00
|
|
|
},
|
|
|
|
setBlurState () {
|
|
|
|
this.errorBlurState = true
|
|
|
|
this.focusState = false
|
|
|
|
},
|
|
|
|
setFocusState () {
|
|
|
|
this.focusState = true
|
2019-09-24 18:28:15 +03:00
|
|
|
},
|
|
|
|
createOptionList (options) {
|
|
|
|
if (!Array.isArray(options)) {
|
|
|
|
return reduce(options, (options, value, label) => options.concat({value, label, id: shortid.generate()}), [])
|
|
|
|
} else if (Array.isArray(options) && !options.length) {
|
|
|
|
return [{value: this.name, label: (this.label || this.name), id: shortid.generate()}]
|
|
|
|
}
|
|
|
|
return options
|
2018-01-31 01:21:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|