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",
|
||||
"description": "The easiest way to build forms in Vue.",
|
||||
"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…
Reference in New Issue
Block a user