Initial stable build of vue-formulate
This commit is contained in:
parent
a705eced91
commit
9011888971
92
README.md
Normal file
92
README.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# Vue Formulate
|
||||||
|
---------------
|
||||||
|
|
||||||
|
### What is it?
|
||||||
|
|
||||||
|
Vue Formulate is a (Vue)[https://vuejs.org/] plugin that exposes an elegant
|
||||||
|
mechanism for building and validating forms with a centralized data store.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
First download the `vue-formulate` package from npm:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install vue-formulate
|
||||||
|
```
|
||||||
|
|
||||||
|
Install `vue-formulate` like any other vue plugin:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Vue from 'vue
|
||||||
|
import formulate from 'vue-formulate;
|
||||||
|
|
||||||
|
Vue.use(formulate)
|
||||||
|
```
|
||||||
|
Finally `vue-formulate` needs to access your vuex store. You can choose to.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
`vue-formulate` automatically registers two components `formulate` and
|
||||||
|
`formulate-element`. These two elements are able to address most of your form
|
||||||
|
building needs. Here's a simple example:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<formulate name="registration">
|
||||||
|
<formulate-element
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
label="What is your name?"
|
||||||
|
validation="required"
|
||||||
|
/>
|
||||||
|
<formulate-element
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
label="What is your email address?"
|
||||||
|
validation="required(Email address)|email"
|
||||||
|
/>
|
||||||
|
<formulate-element
|
||||||
|
type="submit"
|
||||||
|
name="Register"
|
||||||
|
/>
|
||||||
|
</formulate>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validation Rules
|
||||||
|
|
||||||
|
There are several built in validation methods and you are easily able to add
|
||||||
|
your own.
|
||||||
|
|
||||||
|
Rule | Arguments
|
||||||
|
----------|---------------
|
||||||
|
required | label
|
||||||
|
email | label
|
||||||
|
confirmed | label, confirmation field
|
||||||
|
|
||||||
|
You can add as many validation rules as you want to each `formulate-element`,
|
||||||
|
simply chain your rules with pipes `|'.
|
||||||
|
|
||||||
|
```
|
||||||
|
validation="required(My Label)|confirmed(Password Field, confirmation_field)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Adding your own validation rules is simple, simply pass an additional object
|
||||||
|
of rules in your installation:
|
||||||
|
|
||||||
|
```js
|
||||||
|
Vue.use(formulate, {
|
||||||
|
rules: {
|
||||||
|
isPizza: ({field, value, error, values}, label) => value === 'pizza' ? false : `${label || field} is not pizza.`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Validation rules expect a return of `false` if there are no errors, or a error
|
||||||
|
message string. Validation rules are all passed an object with the `field` name,
|
||||||
|
`value` of the field, `error` function to generate an error message, and all the
|
||||||
|
`values` of the entire form.
|
||||||
|
|
||||||
|
### Full Documentation
|
||||||
|
|
||||||
|
There are many more options available, more documentation coming soon.
|
44
dist/index.js
vendored
44
dist/index.js
vendored
File diff suppressed because one or more lines are too long
88
package-lock.json
generated
88
package-lock.json
generated
@ -1943,32 +1943,6 @@
|
|||||||
"wordwrap": "0.0.2"
|
"wordwrap": "0.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clone-deep": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-kWn5hGUnIA4algk62xJIp9jxQZ8DxSPg9ktkkK1WxRGhU/0GKZBekYJHXAXaZKMpxoq/7R4eygeIl9Cf7si+bA==",
|
|
||||||
"requires": {
|
|
||||||
"for-own": "1.0.0",
|
|
||||||
"is-plain-object": "2.0.4",
|
|
||||||
"kind-of": "6.0.2",
|
|
||||||
"shallow-clone": "2.0.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"for-own": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
|
|
||||||
"requires": {
|
|
||||||
"for-in": "1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"kind-of": {
|
|
||||||
"version": "6.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
|
|
||||||
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"co": {
|
"co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
@ -3079,7 +3053,8 @@
|
|||||||
"for-in": {
|
"for-in": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||||
"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
|
"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"for-own": {
|
"for-own": {
|
||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
@ -4612,21 +4587,6 @@
|
|||||||
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
|
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-plain-object": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
|
||||||
"requires": {
|
|
||||||
"isobject": "3.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"isobject": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
|
||||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-posix-bracket": {
|
"is-posix-bracket": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
|
||||||
@ -5203,25 +5163,6 @@
|
|||||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mixin-object": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-RsUqTd3DyF9+UPqhLzJIWwGm4ZGIPYOu6WcQhQuBqqVBGhc6LOC8LrFk9KD7PvVwmqri45IJT88WLrNNrMWjxg==",
|
|
||||||
"requires": {
|
|
||||||
"for-in": "1.0.2",
|
|
||||||
"is-extendable": "1.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"is-extendable": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
|
||||||
"requires": {
|
|
||||||
"is-plain-object": "2.0.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||||
@ -6362,31 +6303,6 @@
|
|||||||
"safe-buffer": "5.1.1"
|
"safe-buffer": "5.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shallow-clone": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-2o81AG/RpLTAG/ZXQekPtH/6yTffzKlJ+i6UhtVTtnP6zWQaNo9vt6LI28bhZLSesB12VQSfJYtXopTogVBveg==",
|
|
||||||
"requires": {
|
|
||||||
"is-extendable": "1.0.1",
|
|
||||||
"kind-of": "6.0.2",
|
|
||||||
"mixin-object": "3.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"is-extendable": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
|
||||||
"requires": {
|
|
||||||
"is-plain-object": "2.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"kind-of": {
|
|
||||||
"version": "6.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
|
|
||||||
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||||
|
@ -59,7 +59,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clone-deep": "^3.0.1",
|
|
||||||
"shortid": "^2.2.8"
|
"shortid": "^2.2.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,20 @@ export default {
|
|||||||
initial: {
|
initial: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
|
},
|
||||||
|
behavior: {
|
||||||
|
type: String,
|
||||||
|
default: 'blur'
|
||||||
|
},
|
||||||
|
showErrors: {
|
||||||
|
type: [Boolean, Object],
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
parentIdentifier: 'vue-formulate-wrapper-element'
|
parentIdentifier: 'vue-formulate-wrapper-element',
|
||||||
|
forceErrors: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -50,6 +59,15 @@ export default {
|
|||||||
},
|
},
|
||||||
fields () {
|
fields () {
|
||||||
return this.$formulate.fields(this.$vnode)
|
return this.$formulate.fields(this.$vnode)
|
||||||
|
},
|
||||||
|
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'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
@ -87,8 +105,8 @@ export default {
|
|||||||
field,
|
field,
|
||||||
value: this.values[field]
|
value: this.values[field]
|
||||||
}, validation, this.values)
|
}, validation, this.values)
|
||||||
if (!equals(errors, (this.validationErrors[field] || []))) {
|
if (!equals(errors || [], (this.validationErrors[field] || []))) {
|
||||||
this.updateFieldValidationErrors({field, errors})
|
this.updateFieldValidationErrors({field, errors: errors || []})
|
||||||
}
|
}
|
||||||
return errors
|
return errors
|
||||||
},
|
},
|
||||||
@ -99,8 +117,12 @@ export default {
|
|||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
submit () {
|
submit () {
|
||||||
|
if (this.hasErrors) {
|
||||||
|
this.forceErrors = true
|
||||||
|
} else {
|
||||||
alert('submitting form')
|
alert('submitting form')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,39 +1,91 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="formulate-element">
|
<div :class="classes">
|
||||||
<div class="formulate-element-input-wrapper">
|
<div class="formulate-element-input-wrapper">
|
||||||
<!-- TEXT STYLE INPUTS -->
|
<!-- TEXT STYLE INPUTS -->
|
||||||
<label
|
<label
|
||||||
:for="id"
|
:for="id"
|
||||||
v-text="label"
|
v-text="label"
|
||||||
v-if="label && isTextInput"
|
v-if="label && (!isBoxInput || optionList.length > 1)"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
ref="input"
|
ref="input"
|
||||||
:type="type"
|
:type="type"
|
||||||
:name="name"
|
:name="name"
|
||||||
:id="id"
|
|
||||||
v-model="val"
|
v-model="val"
|
||||||
|
v-bind="attributes"
|
||||||
v-if="isTextInput"
|
v-if="isTextInput"
|
||||||
|
@blur="errorBlurState = true"
|
||||||
>
|
>
|
||||||
<!-- BUTTON INPUTS -->
|
<!-- BUTTON INPUTS -->
|
||||||
<button
|
<button
|
||||||
:type="type"
|
:type="type"
|
||||||
v-text="label || name"
|
v-text="label || name"
|
||||||
v-if="isButtonInput"
|
v-if="isButtonInput"
|
||||||
:disabled="type === 'submit' && form.hasErrors"
|
:disabled="type === 'submit' && (form.hasErrors && form.behavior === 'live')"
|
||||||
/>
|
/>
|
||||||
<!-- SELECT INPUTS -->
|
<!-- SELECT INPUTS -->
|
||||||
|
<select
|
||||||
<!-- CHECKBOX INPUTS -->
|
v-bind="attributes"
|
||||||
|
v-if="isSelectInput"
|
||||||
<!-- RADIO INPUTS -->
|
:name="name"
|
||||||
|
v-model="val"
|
||||||
|
@blur="errorBlurState = true"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in optionList"
|
||||||
|
:value="option.value"
|
||||||
|
:key="option.id"
|
||||||
|
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>
|
||||||
<!-- CUSTOM SLOT INPUTS -->
|
<!-- CUSTOM SLOT INPUTS -->
|
||||||
<slot v-if="hasCustomInput" />
|
<slot v-if="hasCustomInput" />
|
||||||
|
|
||||||
|
<!-- UNSUPORTED INPUT -->
|
||||||
|
<div
|
||||||
|
style="background-color: red; color: white"
|
||||||
|
v-if="isUnsupportedInput"
|
||||||
|
v-text="`Unsupported field type: “${type}”.`"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
class="formulate-errors"
|
class="formulate-errors"
|
||||||
v-if="localAndValidationErrors.length"
|
v-if="shouldShowErrors && localAndValidationErrors.length"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
v-for="error in localAndValidationErrors"
|
v-for="error in localAndValidationErrors"
|
||||||
@ -45,7 +97,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {inputTypes} from '../utils'
|
import {inputTypes, equals, reduce} from '../utils'
|
||||||
import shortid from 'shortid'
|
import shortid from 'shortid'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -77,6 +129,35 @@ export default {
|
|||||||
id: {
|
id: {
|
||||||
type: [String],
|
type: [String],
|
||||||
default: () => shortid.generate()
|
default: () => shortid.generate()
|
||||||
|
},
|
||||||
|
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: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
errorBlurState: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -89,8 +170,14 @@ export default {
|
|||||||
isButtonInput () {
|
isButtonInput () {
|
||||||
return !this.hasCustomInput && inputTypes.button.includes(this.type)
|
return !this.hasCustomInput && inputTypes.button.includes(this.type)
|
||||||
},
|
},
|
||||||
isListInput () {
|
isSelectInput () {
|
||||||
return !this.hasCustomInput && inputTypes.list.includes(this.type)
|
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)
|
||||||
},
|
},
|
||||||
form () {
|
form () {
|
||||||
let parent = this.$parent
|
let parent = this.$parent
|
||||||
@ -106,7 +193,20 @@ export default {
|
|||||||
return this.form.values
|
return this.form.values
|
||||||
},
|
},
|
||||||
value () {
|
value () {
|
||||||
return this.values[this.name]
|
let value = this.values[this.name]
|
||||||
|
if (value === undefined) {
|
||||||
|
switch (this.type) {
|
||||||
|
case 'color':
|
||||||
|
value = '#000000'
|
||||||
|
break
|
||||||
|
case 'checkbox':
|
||||||
|
if (this.optionList.length > 1) {
|
||||||
|
value = []
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
},
|
},
|
||||||
module () {
|
module () {
|
||||||
return this.form.$props['module']
|
return this.form.$props['module']
|
||||||
@ -114,6 +214,13 @@ export default {
|
|||||||
formName () {
|
formName () {
|
||||||
return this.form.$props['name']
|
return this.form.$props['name']
|
||||||
},
|
},
|
||||||
|
classes () {
|
||||||
|
return {
|
||||||
|
'formulate-element': true,
|
||||||
|
'formulate-element--has-value': !!this.value,
|
||||||
|
'formulate-element--has-errors': this.localAndValidationErrors.length && this.shouldShowErrors
|
||||||
|
}
|
||||||
|
},
|
||||||
validationErrors () {
|
validationErrors () {
|
||||||
return this.form.validationErrors[this.name] || []
|
return this.form.validationErrors[this.name] || []
|
||||||
},
|
},
|
||||||
@ -123,13 +230,38 @@ export default {
|
|||||||
localAndValidationErrors () {
|
localAndValidationErrors () {
|
||||||
return this.errors.concat(this.validationErrors)
|
return this.errors.concat(this.validationErrors)
|
||||||
},
|
},
|
||||||
|
shouldShowErrors () {
|
||||||
|
let show = this.form.shouldShowErrors
|
||||||
|
if (this.form.behavior === 'blur') {
|
||||||
|
show = this.errorBlurState
|
||||||
|
}
|
||||||
|
if (this.showErrors === false || this.showErrors === true) {
|
||||||
|
show = this.showErrors
|
||||||
|
}
|
||||||
|
return show
|
||||||
|
},
|
||||||
attributes () {
|
attributes () {
|
||||||
return this.$props
|
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
|
||||||
},
|
},
|
||||||
val: {
|
val: {
|
||||||
set (value) {
|
set (value) {
|
||||||
this.form.update({field: this.name, value})
|
this.form.update({field: this.name, value})
|
||||||
|
if (this.isTextInput) {
|
||||||
this.$refs.input.value = value
|
this.$refs.input.value = value
|
||||||
|
}
|
||||||
},
|
},
|
||||||
get () {
|
get () {
|
||||||
return this.value
|
return this.value
|
||||||
@ -137,12 +269,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
errors () {
|
localAndValidationErrors () {
|
||||||
|
if (!equals(this.localAndValidationErrors, this.storeErrors)) {
|
||||||
this.form.updateFieldErrors({
|
this.form.updateFieldErrors({
|
||||||
field: this.name,
|
field: this.name,
|
||||||
errors: this.localAndValidationErrors
|
errors: this.localAndValidationErrors
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
if (this.initial !== false) {
|
if (this.initial !== false) {
|
||||||
|
@ -5,7 +5,7 @@ export default {
|
|||||||
* @param {string} label
|
* @param {string} label
|
||||||
*/
|
*/
|
||||||
async required ({field, value, error}, label) {
|
async required ({field, value, error}, label) {
|
||||||
return (!value) ? error(...arguments) : false
|
return (!value || (Array.isArray(value) && !value.length)) ? error(...arguments) : false
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
11
src/utils.js
11
src/utils.js
@ -1,5 +1,3 @@
|
|||||||
import cloneDeep from 'clone-deep'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare the equality of two arrays.
|
* Compare the equality of two arrays.
|
||||||
* @param {Array} arr1
|
* @param {Array} arr1
|
||||||
@ -22,7 +20,7 @@ export function equals (arr1, arr2) {
|
|||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
*/
|
*/
|
||||||
export function map (original, callback) {
|
export function map (original, callback) {
|
||||||
let obj = cloneDeep(original)
|
let obj = Object.assign({}, original)
|
||||||
for (let key in obj) {
|
for (let key in obj) {
|
||||||
obj[key] = callback(key, obj[key])
|
obj[key] = callback(key, obj[key])
|
||||||
}
|
}
|
||||||
@ -71,7 +69,6 @@ export const inputTypes = {
|
|||||||
'hidden',
|
'hidden',
|
||||||
'month',
|
'month',
|
||||||
'password',
|
'password',
|
||||||
'radio',
|
|
||||||
'range',
|
'range',
|
||||||
'search',
|
'search',
|
||||||
'tel',
|
'tel',
|
||||||
@ -83,7 +80,11 @@ export const inputTypes = {
|
|||||||
'submit',
|
'submit',
|
||||||
'button'
|
'button'
|
||||||
],
|
],
|
||||||
list: [
|
select: [
|
||||||
'select'
|
'select'
|
||||||
|
],
|
||||||
|
box: [
|
||||||
|
'radio',
|
||||||
|
'checkbox'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -41,3 +41,15 @@ test('tests multiple validation errors', async t => {
|
|||||||
test('tests empty validation string', async t => {
|
test('tests empty validation string', async t => {
|
||||||
t.is(false, await formulate.validationErrors({field: 'email', value: 'pastaparty'}, false))
|
t.is(false, await formulate.validationErrors({field: 'email', value: 'pastaparty'}, false))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('can extend rules and errors', async t => {
|
||||||
|
formulate.install(VueMock, {
|
||||||
|
rules: {
|
||||||
|
isPizza: ({field, value, error}, label) => value === 'pizza' ? false : error({field, value})
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
isPizza: ({field, value}) => `${field} is not a pizza`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.deepEqual(['pepperoni is not a pizza'], await formulate.validationErrors({field: 'pepperoni', value: 'meat'}, 'isPizza'))
|
||||||
|
})
|
||||||
|
@ -12,6 +12,11 @@ test('test required rule failure', async t => {
|
|||||||
t.is('namexyz', v)
|
t.is('namexyz', v)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('test required rule empty array failure', async t => {
|
||||||
|
let v = await rules.required({field: 'name', value: [], error}, 'xyz')
|
||||||
|
t.is('namexyz', v)
|
||||||
|
})
|
||||||
|
|
||||||
test('test required rule passes', async t => {
|
test('test required rule passes', async t => {
|
||||||
t.is(false, await rules.required({field: 'name', value: 'Justin'}))
|
t.is(false, await rules.required({field: 'name', value: 'Justin'}))
|
||||||
})
|
})
|
||||||
|
147
tests/store.test.js
Normal file
147
tests/store.test.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import test from 'ava'
|
||||||
|
import {formulateState, formulateGetters, formulateMutations} from '../dist'
|
||||||
|
|
||||||
|
test('initial store state', async t => {
|
||||||
|
t.deepEqual({
|
||||||
|
values: {},
|
||||||
|
errors: {},
|
||||||
|
validationErrors: {}
|
||||||
|
}, formulateState()())
|
||||||
|
})
|
||||||
|
|
||||||
|
test('extended initial store state', async t => {
|
||||||
|
t.deepEqual({
|
||||||
|
values: {},
|
||||||
|
errors: {},
|
||||||
|
validationErrors: {},
|
||||||
|
additionalParam: 'test'
|
||||||
|
}, formulateState({
|
||||||
|
additionalParam: 'test'
|
||||||
|
})())
|
||||||
|
})
|
||||||
|
|
||||||
|
test('validationErrors getter', async t => {
|
||||||
|
let state = {
|
||||||
|
validationErrors: {
|
||||||
|
form: {
|
||||||
|
field: ['This is an error']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.is(formulateGetters().formValidationErrors(state), state.validationErrors)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('errors getter', async t => {
|
||||||
|
let state = {
|
||||||
|
errors: {
|
||||||
|
form: {
|
||||||
|
field: ['This is an error', 'second error']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.is(formulateGetters().formErrors(state), state.errors)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('values getter', async t => {
|
||||||
|
let state = {
|
||||||
|
values: {
|
||||||
|
form: {
|
||||||
|
name: 'Johan',
|
||||||
|
field: 'Guttenberg'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.is(formulateGetters().formValues(state), state.values)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('form has errors', async t => {
|
||||||
|
let state = {
|
||||||
|
errors: {
|
||||||
|
form: {
|
||||||
|
field: ['This is an error', 'second error']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.is(true, formulateGetters().hasErrors(state).form)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('form has empty errors', async t => {
|
||||||
|
let state = {
|
||||||
|
errors: {
|
||||||
|
form: {
|
||||||
|
field: [],
|
||||||
|
other: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.is(false, formulateGetters().hasErrors(state).form)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('form has no errors', async t => {
|
||||||
|
let state = {
|
||||||
|
errors: {
|
||||||
|
form: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.is(false, formulateGetters().hasErrors(state).form)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('adds a new field value', async t => {
|
||||||
|
let state = {values: {}}
|
||||||
|
formulateMutations().setFieldValue(state, {
|
||||||
|
form: 'form',
|
||||||
|
field: 'name',
|
||||||
|
value: 'test name'
|
||||||
|
})
|
||||||
|
t.deepEqual(state.values, {form: {name: 'test name'}})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('adds an existing field value', async t => {
|
||||||
|
let state = {values: {form: {name: 'old name'}}}
|
||||||
|
formulateMutations().setFieldValue(state, {
|
||||||
|
form: 'form',
|
||||||
|
field: 'name',
|
||||||
|
value: 'new name'
|
||||||
|
})
|
||||||
|
t.deepEqual(state.values, {form: {name: 'new name'}})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('adds an error to new field', async t => {
|
||||||
|
let state = {errors: {}}
|
||||||
|
formulateMutations().setFieldErrors(state, {
|
||||||
|
form: 'form',
|
||||||
|
field: 'name',
|
||||||
|
errors: ['i dislike this field']
|
||||||
|
})
|
||||||
|
t.deepEqual(state.errors, {form: {name: ['i dislike this field']}})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('adds an error to existing field', async t => {
|
||||||
|
let state = {errors: {form: {name: ['i like this field']}}}
|
||||||
|
formulateMutations().setFieldErrors(state, {
|
||||||
|
form: 'form',
|
||||||
|
field: 'name',
|
||||||
|
errors: ['i dislike this field']
|
||||||
|
})
|
||||||
|
t.deepEqual(state.errors, {form: {name: ['i dislike this field']}})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('adds a validationError to new field', async t => {
|
||||||
|
let state = {validationErrors: {}}
|
||||||
|
formulateMutations().setFieldValidationErrors(state, {
|
||||||
|
form: 'form',
|
||||||
|
field: 'name',
|
||||||
|
errors: ['i dislike this field']
|
||||||
|
})
|
||||||
|
t.deepEqual(state.validationErrors, {form: {name: ['i dislike this field']}})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('adds a validationError to existing field', async t => {
|
||||||
|
let state = {validationErrors: {form: {name: ['i like this field']}}}
|
||||||
|
formulateMutations().setFieldValidationErrors(state, {
|
||||||
|
form: 'form',
|
||||||
|
field: 'name',
|
||||||
|
errors: ['i dislike this field']
|
||||||
|
})
|
||||||
|
t.deepEqual(state.validationErrors, {form: {name: ['i dislike this field']}})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user