Removes the original source code
This commit is contained in:
parent
778d6b8731
commit
6690ab3576
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@braid/vue-formulate-next",
|
"name": "@braid/vue-formulate",
|
||||||
"version": "2.0.0-alpha.1",
|
"version": "2.0.0-alpha.1",
|
||||||
"description": "The easiest way to build forms in Vue.",
|
"description": "The easiest way to build forms in Vue.",
|
||||||
"main": "dist/formulate.umd.js",
|
"main": "dist/formulate.umd.js",
|
||||||
|
@ -1,181 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form
|
|
||||||
@submit.prevent="submit"
|
|
||||||
class="formulate-form"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {equals, reduce} from '../utils'
|
|
||||||
import cloneDeep from 'clone-deep'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
type: [String, Boolean],
|
|
||||||
default: function () {
|
|
||||||
return this.$formulate.options.vuexModule
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initial: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
behavior: {
|
|
||||||
type: String,
|
|
||||||
default: 'blur'
|
|
||||||
},
|
|
||||||
showErrors: {
|
|
||||||
type: [Boolean, Object],
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
errors: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
prevent: {
|
|
||||||
type: String,
|
|
||||||
default: 'validation'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
parentIdentifier: 'vue-formulate-wrapper-element',
|
|
||||||
forceErrors: null,
|
|
||||||
fieldInitials: {},
|
|
||||||
whenFinishedValidating: Promise.resolve()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
m () {
|
|
||||||
return `${this.module ? this.module + '/' : ''}`
|
|
||||||
},
|
|
||||||
hasErrors () {
|
|
||||||
return this.$store.getters[`${this.m}hasErrors`][this.name] || false
|
|
||||||
},
|
|
||||||
hasValidationErrors () {
|
|
||||||
return this.$store.getters[`${this.m}hasValidationErrors`][this.name] || false
|
|
||||||
},
|
|
||||||
values () {
|
|
||||||
return cloneDeep(this.$store.getters[`${this.m}formValues`][this.name] || {})
|
|
||||||
},
|
|
||||||
storeErrors () {
|
|
||||||
return this.$store.getters[`${this.m}formErrors`][this.name] || {}
|
|
||||||
},
|
|
||||||
validationErrors () {
|
|
||||||
return this.$store.getters[`${this.m}formValidationErrors`][this.name] || {}
|
|
||||||
},
|
|
||||||
fields () {
|
|
||||||
return this.$store.getters[`${this.m}formMeta`][this.name] || []
|
|
||||||
},
|
|
||||||
shouldShowErrors () {
|
|
||||||
if (this.forceErrors === false || this.forceErrors === true) {
|
|
||||||
return this.forceErrors
|
|
||||||
}
|
|
||||||
if (this.showErrors === false || this.showErrors === true) {
|
|
||||||
return this.showErrors
|
|
||||||
}
|
|
||||||
return this.behavior === 'live'
|
|
||||||
},
|
|
||||||
mergedInitial () {
|
|
||||||
return Object.assign({}, this.initial, this.fieldInitials)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
mergedInitial () {
|
|
||||||
this.hydrate(this.mergedInitial)
|
|
||||||
},
|
|
||||||
values () {
|
|
||||||
this.updateFormValidation()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.hydrate(this.mergedInitial)
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
this.hydrate(this.mergedInitial)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
registerField (field, data) {
|
|
||||||
this.$store.commit(`${this.m}setFieldMeta`, {form: this.name, field, data})
|
|
||||||
if (data.type !== 'submit') {
|
|
||||||
this.$store.commit(`${this.m}setFieldValue`, {
|
|
||||||
field,
|
|
||||||
value: this.mergedInitial.hasOwnProperty(field) ? this.mergedInitial[field] : undefined,
|
|
||||||
form: this.name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async deregisterField (field) {
|
|
||||||
await this.whenFinishedValidating
|
|
||||||
this.$store.commit(`${this.m}removeField`, {
|
|
||||||
form: this.name,
|
|
||||||
field
|
|
||||||
})
|
|
||||||
},
|
|
||||||
hydrate (values) {
|
|
||||||
for (let field of this.fields) {
|
|
||||||
if (field.type !== 'submit' && typeof this.values[field.name] === 'undefined') {
|
|
||||||
this.$store.commit(`${this.m}setFieldValue`, {
|
|
||||||
field: field.name,
|
|
||||||
value: values[field.name],
|
|
||||||
form: this.name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setInitial (field, value) {
|
|
||||||
this.fieldInitials = Object.assign({}, this.fieldInitials, {[field]: value})
|
|
||||||
},
|
|
||||||
update (change) {
|
|
||||||
this.$store.commit(`${this.m}setFieldValue`, Object.assign(change, {
|
|
||||||
form: this.name
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
updateFieldErrors (change) {
|
|
||||||
this.$store.commit(`${this.m}setFieldErrors`, Object.assign(change, {
|
|
||||||
form: this.name
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
updateFieldValidationErrors (change) {
|
|
||||||
this.$store.commit(`${this.m}setFieldValidationErrors`, Object.assign(change, {
|
|
||||||
form: this.name
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
async validateField ({field, validation, label}) {
|
|
||||||
let errors = await this.$formulate.validationErrors({
|
|
||||||
field,
|
|
||||||
value: this.values[field],
|
|
||||||
label
|
|
||||||
}, validation, this.values)
|
|
||||||
if (!equals(errors || [], (this.validationErrors[field] || []))) {
|
|
||||||
if (this.fields.find(f => f.name === field)) {
|
|
||||||
this.updateFieldValidationErrors({field, errors: errors || []})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors
|
|
||||||
},
|
|
||||||
async updateFormValidation () {
|
|
||||||
await this.whenFinishedValidating
|
|
||||||
this.whenFinishedValidating = Promise.all(this.fields.map(async field => this.validateField({
|
|
||||||
field: field.name,
|
|
||||||
validation: field.validation,
|
|
||||||
label: field.validationLabel || field.label || field.name
|
|
||||||
})))
|
|
||||||
},
|
|
||||||
submit () {
|
|
||||||
if ((this.prevent === 'validation' && this.hasValidationErrors) || (this.prevent === 'any' && this.hasErrors)) {
|
|
||||||
this.forceErrors = true
|
|
||||||
} else {
|
|
||||||
this.$emit('submit', Object.assign({}, this.values))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,458 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div :class="classes">
|
|
||||||
<slot name="prefix" />
|
|
||||||
<div
|
|
||||||
class="formulate-element-input-wrapper"
|
|
||||||
:data-type="type"
|
|
||||||
:data-classification="classification"
|
|
||||||
:data-is-disabled="disabled"
|
|
||||||
>
|
|
||||||
<!-- TEXT STYLE INPUTS -->
|
|
||||||
<label
|
|
||||||
:for="id"
|
|
||||||
v-text="label"
|
|
||||||
v-if="label && (!isBoxInput || optionList.length > 1)"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
ref="input"
|
|
||||||
:class="elementClasses"
|
|
||||||
:type="type"
|
|
||||||
:name="name"
|
|
||||||
v-model="val"
|
|
||||||
v-bind="attributes"
|
|
||||||
v-if="isTextInput"
|
|
||||||
@blur="setBlurState"
|
|
||||||
@focus="setFocusState"
|
|
||||||
:disabled="disabled"
|
|
||||||
:step="step"
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
ref="textarea"
|
|
||||||
:class="elementClasses"
|
|
||||||
:type="type"
|
|
||||||
:name="name"
|
|
||||||
v-model="val"
|
|
||||||
v-bind="attributes"
|
|
||||||
v-if="isTextareaInput"
|
|
||||||
@blur="setBlurState"
|
|
||||||
@focus="setFocusState"
|
|
||||||
:disabled="disabled"
|
|
||||||
/>
|
|
||||||
<!-- BUTTON INPUTS -->
|
|
||||||
<button
|
|
||||||
:type="type"
|
|
||||||
:class="elementClasses"
|
|
||||||
v-if="isButtonInput"
|
|
||||||
:disabled="disabled || (type === 'submit' && (form.hasErrors && form.behavior === 'live'))"
|
|
||||||
>
|
|
||||||
<slot
|
|
||||||
v-if="$slots.button"
|
|
||||||
name="button"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
v-text="label || name"
|
|
||||||
v-else
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<!-- SELECT INPUTS -->
|
|
||||||
<select
|
|
||||||
v-bind="attributes"
|
|
||||||
v-if="isSelectInput"
|
|
||||||
:class="elementClasses"
|
|
||||||
:name="name"
|
|
||||||
v-model="val"
|
|
||||||
@blur="setBlurState"
|
|
||||||
@focus="setFocusState"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</select>
|
|
||||||
<!-- BOX INPUTS -->
|
|
||||||
<div
|
|
||||||
class="formulate-element-box-input-wrap"
|
|
||||||
v-if="isBoxInput"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="formulate-element-box-input-group"
|
|
||||||
v-for="option in optionList"
|
|
||||||
:key="option.id"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
:class="elementClasses"
|
|
||||||
:name="name"
|
|
||||||
:id="option.id"
|
|
||||||
:value="option.value"
|
|
||||||
:key="`${option.id}-input`"
|
|
||||||
v-bind="attributes"
|
|
||||||
v-model="val"
|
|
||||||
v-if="type === 'radio'"
|
|
||||||
@blur="setBlurState"
|
|
||||||
@focus="setFocusState"
|
|
||||||
:disabled="disabled || option.disabled"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
:class="elementClasses"
|
|
||||||
:name="name"
|
|
||||||
:id="option.id"
|
|
||||||
:value="option.value"
|
|
||||||
:key="`${option.id}-input`"
|
|
||||||
v-bind="attributes"
|
|
||||||
v-model="val"
|
|
||||||
v-if="type === 'checkbox'"
|
|
||||||
@blur="setBlurState"
|
|
||||||
@focus="setFocusState"
|
|
||||||
:disabled="disabled || option.disabled"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
:for="option.id"
|
|
||||||
:key="`${option.id}-label`"
|
|
||||||
v-text="option.label"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- CUSTOM SLOT INPUTS -->
|
|
||||||
<slot v-if="hasCustomInput" />
|
|
||||||
|
|
||||||
<!-- UNSUPPORTED INPUT -->
|
|
||||||
<div
|
|
||||||
style="background-color: red; color: white"
|
|
||||||
v-if="isUnsupportedInput"
|
|
||||||
v-text="`Unsupported field type: “${type}”.`"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="formulate-help"
|
|
||||||
v-if="help"
|
|
||||||
v-text="help"
|
|
||||||
/>
|
|
||||||
<transition
|
|
||||||
name="formulate-errors"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
<slot name="suffix" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {inputTypes, equals, reduce, filter} from '../utils'
|
|
||||||
import shortid from 'shortid'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
type: {
|
|
||||||
type: [String, Boolean],
|
|
||||||
default: 'text'
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
initial: {
|
|
||||||
type: [String, Number, Boolean],
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
validation: {
|
|
||||||
type: [String, Boolean],
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
errors: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
type: [String, Boolean],
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
id: {
|
|
||||||
type: [String],
|
|
||||||
default: () => shortid.generate()
|
|
||||||
},
|
|
||||||
min: {
|
|
||||||
type: [String, Number, Boolean],
|
|
||||||
default: () => false
|
|
||||||
},
|
|
||||||
max: {
|
|
||||||
type: [String, Number, Boolean],
|
|
||||||
default: () => false
|
|
||||||
},
|
|
||||||
maxlength: {
|
|
||||||
type: [String, Number, Boolean],
|
|
||||||
default: () => false
|
|
||||||
},
|
|
||||||
pattern: {
|
|
||||||
type: [String, Number, Boolean],
|
|
||||||
default: () => false
|
|
||||||
},
|
|
||||||
minlength: {
|
|
||||||
type: [String, Number, Boolean],
|
|
||||||
default: () => false
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: [String, Number, Boolean],
|
|
||||||
default: () => false
|
|
||||||
},
|
|
||||||
step: {
|
|
||||||
type: [String, Number, Boolean],
|
|
||||||
default: () => false
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: [Object, Array],
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
optionGroups: {
|
|
||||||
type: [Boolean, Object],
|
|
||||||
default: false,
|
|
||||||
validator: function (value) {
|
|
||||||
if (value === false) {
|
|
||||||
return true
|
|
||||||
} else if (typeof value === 'boolean') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
multiple: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
showErrors: {
|
|
||||||
type: [Object, Boolean],
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
validationLabel: {
|
|
||||||
type: [String, Boolean],
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
elementClasses: {
|
|
||||||
type: [String, Array, Object],
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
help: {
|
|
||||||
type: [Boolean, String],
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
errorBlurState: false,
|
|
||||||
focusState: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
classification () {
|
|
||||||
if (this.isTextInput) return 'text'
|
|
||||||
if (this.isTextareaInput) return 'textarea'
|
|
||||||
if (this.isBoxInput) return 'box'
|
|
||||||
if (this.isButtonInput) return 'button'
|
|
||||||
if (this.isSelectInput) return 'select'
|
|
||||||
if (this.hasCustomInput) return 'custom'
|
|
||||||
return 'unsupported'
|
|
||||||
},
|
|
||||||
hasCustomInput () {
|
|
||||||
return (this.$slots.default && this.$slots.default.length)
|
|
||||||
},
|
|
||||||
isTextInput () {
|
|
||||||
return !this.hasCustomInput && inputTypes.text.includes(this.type)
|
|
||||||
},
|
|
||||||
isTextareaInput () {
|
|
||||||
return !this.hasCustomInput && inputTypes.textarea.includes(this.type)
|
|
||||||
},
|
|
||||||
isButtonInput () {
|
|
||||||
return !this.hasCustomInput && inputTypes.button.includes(this.type)
|
|
||||||
},
|
|
||||||
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 && !this.isTextareaInput)
|
|
||||||
},
|
|
||||||
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 () {
|
|
||||||
let value = this.values[this.name]
|
|
||||||
if (value === undefined) {
|
|
||||||
switch (this.type) {
|
|
||||||
case 'color':
|
|
||||||
return '#000000'
|
|
||||||
case 'checkbox':
|
|
||||||
if (this.optionList.length > 1) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
},
|
|
||||||
module () {
|
|
||||||
return this.form.$props['module']
|
|
||||||
},
|
|
||||||
formName () {
|
|
||||||
return this.form.$props['name']
|
|
||||||
},
|
|
||||||
classes () {
|
|
||||||
return {
|
|
||||||
'formulate-element': true,
|
|
||||||
[`formulate-element--type--${this.type}`]: true,
|
|
||||||
'formulate-element--has-value': !!this.value,
|
|
||||||
'formulate-element--has-errors': this.localAndValidationErrors.length && this.shouldShowErrors,
|
|
||||||
'formulate-element--has-prefix': !!this.$slots.prefix,
|
|
||||||
'formulate-element--has-suffix': !!this.$slots.suffix,
|
|
||||||
'formulate-element--has-focus': !!this.focusState
|
|
||||||
}
|
|
||||||
},
|
|
||||||
validationErrors () {
|
|
||||||
return this.form.validationErrors[this.name] || []
|
|
||||||
},
|
|
||||||
storeErrors () {
|
|
||||||
return this.form.storeErrors[this.name] || []
|
|
||||||
},
|
|
||||||
formErrors () {
|
|
||||||
return this.form.errors[this.name] || []
|
|
||||||
},
|
|
||||||
localAndValidationErrors () {
|
|
||||||
return this.errors.concat(this.validationErrors).concat(this.formErrors)
|
|
||||||
},
|
|
||||||
shouldShowErrors () {
|
|
||||||
let show = this.form.shouldShowErrors
|
|
||||||
if (this.form.behavior === 'blur') {
|
|
||||||
show = show || this.errorBlurState
|
|
||||||
}
|
|
||||||
if (this.showErrors === false || this.showErrors === true) {
|
|
||||||
show = this.showErrors
|
|
||||||
}
|
|
||||||
return show
|
|
||||||
},
|
|
||||||
attributes () {
|
|
||||||
return ['min', 'max', 'minlength', 'maxlength', 'placeholder', 'id', 'multiple', 'pattern']
|
|
||||||
.filter(prop => this[prop] !== false)
|
|
||||||
.reduce((attributes, attr) => {
|
|
||||||
attributes[attr] = this[attr]
|
|
||||||
return attributes
|
|
||||||
}, {})
|
|
||||||
},
|
|
||||||
optionList () {
|
|
||||||
return this.createOptionList(this.options)
|
|
||||||
},
|
|
||||||
val: {
|
|
||||||
set (value) {
|
|
||||||
this.form.update({field: this.name, value})
|
|
||||||
if (this.isTextInput) {
|
|
||||||
this.$refs.input.value = value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get () {
|
|
||||||
return this.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
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})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setBlurState () {
|
|
||||||
this.errorBlurState = true
|
|
||||||
this.focusState = false
|
|
||||||
},
|
|
||||||
setFocusState () {
|
|
||||||
this.focusState = true
|
|
||||||
},
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,7 +0,0 @@
|
|||||||
export default {
|
|
||||||
required: ({label, value}) => `${label} is required`,
|
|
||||||
email: ({label, value}) => `${label} is invalid.`,
|
|
||||||
confirmed: ({label, value}) => `${label} does not match the confirmation field.`,
|
|
||||||
number: ({label, value}) => `${label} is not a number`,
|
|
||||||
default: ({label, value}) => `This field is invalid.`
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
import FormulateGroup from './components/Formulate'
|
|
||||||
import FormulateElement from './components/FormulateElement'
|
|
||||||
import DefaultRules from './rules'
|
|
||||||
import DefaultErrors from './errors'
|
|
||||||
|
|
||||||
class Formulate {
|
|
||||||
/**
|
|
||||||
* Initialize vue-formulate.
|
|
||||||
*/
|
|
||||||
constructor () {
|
|
||||||
this.defaultOptions = {
|
|
||||||
registerComponents: true,
|
|
||||||
tags: {
|
|
||||||
Formulate: 'formulate',
|
|
||||||
FormulateElement: 'formulate-element'
|
|
||||||
},
|
|
||||||
errors: {},
|
|
||||||
rules: {},
|
|
||||||
vuexModule: false
|
|
||||||
}
|
|
||||||
this.errors = DefaultErrors
|
|
||||||
this.rules = DefaultRules
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Install vue-formulate as an instance of Vue.
|
|
||||||
* @param {Vue} Vue
|
|
||||||
*/
|
|
||||||
install (Vue, options = {}) {
|
|
||||||
Vue.prototype.$formulate = this
|
|
||||||
options = Object.assign(this.defaultOptions, options)
|
|
||||||
if (options.registerComponents) {
|
|
||||||
Vue.component(options.tags.Formulate, FormulateGroup)
|
|
||||||
Vue.component(options.tags.FormulateElement, FormulateElement)
|
|
||||||
}
|
|
||||||
if (options.errors) {
|
|
||||||
this.errors = Object.assign(this.errors, options.errors)
|
|
||||||
}
|
|
||||||
if (options.rules) {
|
|
||||||
this.rules = Object.assign(this.rules, options.rules)
|
|
||||||
}
|
|
||||||
this.options = options
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a string of rules parse them out to relevant pieces/parts
|
|
||||||
* @param {string} rulesString
|
|
||||||
*/
|
|
||||||
parseRules (rulesString) {
|
|
||||||
return rulesString.split('|')
|
|
||||||
.map(rule => rule.trim())
|
|
||||||
.map(rule => rule.match(/([a-zA-Z0-9]+)\((.*)?\)/) || [null, rule, ''])
|
|
||||||
.map(([ruleString, rule, args]) => Object.assign({}, {rule}, args ? {
|
|
||||||
args: args.split(',').map(arg => arg.trim())
|
|
||||||
} : {args: []}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the function that generates a validation error message for a given
|
|
||||||
* validation rule.
|
|
||||||
* @param {string} rule
|
|
||||||
*/
|
|
||||||
errorFactory (rule) {
|
|
||||||
return this.errors[rule] ? this.errors[rule] : this.errors['default']
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a particular field, value, validation rules, and form values
|
|
||||||
* perform asynchronous field validation.
|
|
||||||
* @param {Object} validatee
|
|
||||||
* @param {string} rulesString
|
|
||||||
* @param {Object} values
|
|
||||||
*/
|
|
||||||
async validationErrors ({field, value, label}, rulesString, values) {
|
|
||||||
return rulesString ? Promise.all(
|
|
||||||
this.parseRules(rulesString)
|
|
||||||
.map(({rule, args}) => {
|
|
||||||
if (typeof this.rules[rule] !== 'function') {
|
|
||||||
throw new Error(`Validation rule is invalid: ${rule}`)
|
|
||||||
}
|
|
||||||
return this.rules[rule]({field, value, label, error: this.errorFactory(rule), values}, ...args)
|
|
||||||
})
|
|
||||||
).then(responses => responses.reduce((errors, error) => {
|
|
||||||
return error ? (Array.isArray(errors) ? errors.concat(error) : [error]) : errors
|
|
||||||
}, false)) : false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const formulate = new Formulate()
|
|
||||||
export default formulate
|
|
||||||
export * from './store'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapper to allow bindings to the vuex store for custom fields.
|
|
||||||
* @param {Object} definitions
|
|
||||||
*/
|
|
||||||
export const mapModels = (definitions) => {
|
|
||||||
const models = {}
|
|
||||||
for (let mapTo in definitions) {
|
|
||||||
let [form, field] = definitions[mapTo].split('/')
|
|
||||||
models[mapTo] = {
|
|
||||||
set (value) {
|
|
||||||
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
|
|
||||||
this.$store.commit(`${m}setFieldValue`, {form, field, value})
|
|
||||||
},
|
|
||||||
get () {
|
|
||||||
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
|
|
||||||
if (this.$store.getters[`${m}formValues`][form]) {
|
|
||||||
return this.$store.getters[`${m}formValues`][form][field]
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return models
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
export default {
|
|
||||||
/**
|
|
||||||
* Validate a required field.
|
|
||||||
* @param {Object} field
|
|
||||||
* @param {string} label
|
|
||||||
*/
|
|
||||||
async required ({value, error}) {
|
|
||||||
return (!value || (Array.isArray(value) && !value.length)) ? error(...arguments) : false
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the field contains only numbers
|
|
||||||
* @param {Object} field
|
|
||||||
* @param {string} label
|
|
||||||
*/
|
|
||||||
async number ({value, error}) {
|
|
||||||
return isNaN(value) ? error(...arguments) : false
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate email addresses
|
|
||||||
* @param {Object} field
|
|
||||||
* @param {string} label
|
|
||||||
*/
|
|
||||||
async email ({value, error}) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
var re = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/;
|
|
||||||
return (value && !re.test(value.toLowerCase())) ? error(...arguments) : false
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a particular field is matches another field in the form.
|
|
||||||
* @param {Object} field
|
|
||||||
* @param {string} label
|
|
||||||
* @param {string} confirmField (uses `${field}_confirmation` by default)
|
|
||||||
*/
|
|
||||||
async confirmed ({field, value, error, values}, confirmField) {
|
|
||||||
confirmField = confirmField || `${field}_confirmation`
|
|
||||||
return (value && value !== values[confirmField]) ? error(...arguments) : false
|
|
||||||
}
|
|
||||||
}
|
|
112
src~v1/store.js
112
src~v1/store.js
@ -1,112 +0,0 @@
|
|||||||
import {map, reduce, filter} from './utils'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Curried function for creating the formState
|
|
||||||
* @param {Object} options
|
|
||||||
*/
|
|
||||||
export const formulateState = (options = {}) => () => Object.assign({
|
|
||||||
values: {},
|
|
||||||
errors: {},
|
|
||||||
validationErrors: {},
|
|
||||||
meta: {}
|
|
||||||
}, options)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function for creating the formGetters
|
|
||||||
* @param {string} module
|
|
||||||
* @param {Object} getters
|
|
||||||
*/
|
|
||||||
export const formulateGetters = (moduleName = '', getters = {}) => Object.assign({
|
|
||||||
formValues (state) {
|
|
||||||
return state.values
|
|
||||||
},
|
|
||||||
formErrors (state) {
|
|
||||||
return state.errors
|
|
||||||
},
|
|
||||||
formValidationErrors (state) {
|
|
||||||
return state.validationErrors
|
|
||||||
},
|
|
||||||
formMeta (state) {
|
|
||||||
return reduce(state.meta, (forms, form, fields) => {
|
|
||||||
forms[form] = reduce(fields, (arr, field, data) => arr.concat(data), [])
|
|
||||||
return forms
|
|
||||||
}, {})
|
|
||||||
},
|
|
||||||
hasErrors (state) {
|
|
||||||
return map(state.errors, (form, errors) => {
|
|
||||||
return reduce(errors, (hasErrors, field, errors) => hasErrors || !!errors.length, false)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
hasValidationErrors (state) {
|
|
||||||
return map(state.validationErrors, (form, errors) => {
|
|
||||||
return reduce(errors, (hasErrors, field, errors) => hasErrors || !!errors.length, false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, getters)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function for creating the formActions
|
|
||||||
* @param {string} moduleName
|
|
||||||
* @param {Object} actions
|
|
||||||
*/
|
|
||||||
export const formulateActions = (moduleName = '', actions = {}) => Object.assign({
|
|
||||||
}, actions)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function for creating the formMutations
|
|
||||||
* @param {Object} mutations
|
|
||||||
*/
|
|
||||||
export const formulateMutations = (mutations = {}) => Object.assign({
|
|
||||||
setFieldValue (state, {form, field, value}) {
|
|
||||||
state.values = Object.assign({}, state.values, {
|
|
||||||
[form]: Object.assign({}, state.values[form] || {}, {[field]: value})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setFieldErrors (state, {form, field, errors}) {
|
|
||||||
state.errors = Object.assign({}, state.errors, {
|
|
||||||
[form]: Object.assign({}, state.errors[form] || {}, {[field]: errors})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setFieldValidationErrors (state, {form, field, errors}) {
|
|
||||||
state.validationErrors = Object.assign({}, state.validationErrors, {
|
|
||||||
[form]: Object.assign({}, state.validationErrors[form] || {}, {[field]: errors})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setFieldMeta (state, {form, field, data}) {
|
|
||||||
state.meta = Object.assign({}, state.meta, {
|
|
||||||
[form]: Object.assign({}, state.meta[form] || {}, {[field]: data})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
resetForm (state, form) {
|
|
||||||
if (state.values[form]) {
|
|
||||||
state.values = Object.assign({}, state.values, {
|
|
||||||
[form]: map(state.values[form], (key, value) => undefined)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeField (state, {form, field}) {
|
|
||||||
for (let group in state) {
|
|
||||||
if (state[group][form] && state[group][form].hasOwnProperty(field)) {
|
|
||||||
state[group][form] = filter(state[group][form], (key, value) => key !== field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeFieldValidationErrors (state, {form, field}) {
|
|
||||||
state.validationErrors = Object.assign({}, state.validationErrors, {
|
|
||||||
[form]: filter(state.validationErrors[form] || {}, key => key !== field)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, mutations)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function for exposing a full vuex module.
|
|
||||||
* @param {string} moduleName
|
|
||||||
* @param {Object} validation
|
|
||||||
*/
|
|
||||||
export const formulateModule = (moduleName) => ({
|
|
||||||
state: formulateState(),
|
|
||||||
getters: formulateGetters(moduleName),
|
|
||||||
actions: formulateActions(moduleName),
|
|
||||||
mutations: formulateMutations(),
|
|
||||||
namespaced: true
|
|
||||||
})
|
|
@ -1,95 +0,0 @@
|
|||||||
import cloneDeep from 'clone-deep'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the equality of two arrays.
|
|
||||||
* @param {Array} arr1
|
|
||||||
* @param {Array} arr2
|
|
||||||
*/
|
|
||||||
export function equals (arr1, arr2) {
|
|
||||||
var length = arr1.length
|
|
||||||
if (length !== arr2.length) return false
|
|
||||||
for (var i = 0; i < length; i++) {
|
|
||||||
if (arr1[i] !== arr2[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to map over an object.
|
|
||||||
* @param {Object} obj An object to map over
|
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
|
||||||
export function map (original, callback) {
|
|
||||||
let obj = cloneDeep(original)
|
|
||||||
for (let key in obj) {
|
|
||||||
obj[key] = callback(key, obj[key])
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to filter an object's properties
|
|
||||||
* @param {Object} original
|
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
|
||||||
export function filter (original, callback) {
|
|
||||||
let obj = {}
|
|
||||||
for (let key in original) {
|
|
||||||
if (callback(key, original[key])) {
|
|
||||||
obj[key] = original[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to reduce an object's properties
|
|
||||||
* @param {Object} original
|
|
||||||
* @param {Function} callback
|
|
||||||
* @param {*} accumulator
|
|
||||||
*/
|
|
||||||
export function reduce (original, callback, accumulator) {
|
|
||||||
for (let key in original) {
|
|
||||||
accumulator = callback(accumulator, key, original[key])
|
|
||||||
}
|
|
||||||
return accumulator
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Comprehensive list of input types supported.
|
|
||||||
*/
|
|
||||||
export const inputTypes = {
|
|
||||||
text: [
|
|
||||||
'text',
|
|
||||||
'email',
|
|
||||||
'number',
|
|
||||||
'color',
|
|
||||||
'date',
|
|
||||||
'datetime-local',
|
|
||||||
'hidden',
|
|
||||||
'month',
|
|
||||||
'password',
|
|
||||||
'range',
|
|
||||||
'search',
|
|
||||||
'tel',
|
|
||||||
'time',
|
|
||||||
'url',
|
|
||||||
'week'
|
|
||||||
],
|
|
||||||
button: [
|
|
||||||
'submit',
|
|
||||||
'button'
|
|
||||||
],
|
|
||||||
select: [
|
|
||||||
'select'
|
|
||||||
],
|
|
||||||
box: [
|
|
||||||
'radio',
|
|
||||||
'checkbox'
|
|
||||||
],
|
|
||||||
textarea: [
|
|
||||||
'textarea'
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user